com.squarespace.less.parse.ParseUtils Maven / Gradle / Ivy
The newest version!
/**
* Copyright (c) 2014 SQUARESPACE, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.squarespace.less.parse;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import com.squarespace.less.LessException;
import com.squarespace.less.core.Buffer;
import com.squarespace.less.core.Chars;
import com.squarespace.less.model.ParseError;
public class ParseUtils {
// TODO: future, more efficient location of error line in source
// holding off on this for now as it could produce slight differences in error
// messages which would cause error test cases to break. once the new parser
// is established we can take care of this.
// private static List findErrorOffsets(String raw, int furthest, int lines) {
// System.out.println("string length: " + raw.length());
// List offsets = new ArrayList<>();
// int index = furthest;
// while (index > 0 && lines > 0) {
// int j = raw.lastIndexOf('\n', index - 1);
// if (j == -1) {
// break;
// }
// System.out.println("newline: " + j);
// offsets.add(new int[] { j + 1, index});
// index = j;
// lines--;
// }
// Collections.reverse(offsets);
// return offsets;
//
// System.out.println(offsets.stream().map(e -> Arrays.toString(e)).collect(Collectors.toList()));
//
// for (int[] offset : offsets) {
// System.out.println(StringEscapeUtils.escapeJava(raw.substring(offset[0], offset[1])));
// }
// return offsets;
// }
private static final int WINDOW_SIZE = 74;
private static class Stream {
final String raw;
final int length;
int pos = 0;
Stream(String raw) {
this.raw = raw;
this.length = raw.length();
}
char peek() {
boolean end = pos >= length;
if (end) {
return Chars.EOF;
}
char c = raw.charAt(pos);
return c == Chars.EOF ? Chars.REPLACEMENT : c;
}
void seekTo(char ch) {
while (pos < length) {
char c = peek();
pos++;
if (c == ch) {
break;
}
}
}
}
/**
* Build a user-readable parser error message, showing the exact context for
* the error. We append this to the given exception inside a ParseError node.
*/
public static LessException parseError(LessException exc, Path filePath, String raw, int index) {
List offsets = new ArrayList<>();
Stream stm = new Stream(raw);
// Search for the line that contains our error index.
// Length of the error string
int erridx = 0;
while (stm.peek() != Chars.EOF) {
int start = stm.pos;
erridx = index - start;
stm.seekTo('\n');
int end = stm.pos;
offsets.add(new int[] { start, end });
// Stop when we've found the line that contains the error
if (end > index) {
break;
}
}
// Select the last N lines we collected.
Buffer buf = new Buffer(6);
int size = offsets.size();
int start = Math.max(0, size - 5);
for (int i = start; i < size; i++) {
int[] pos = offsets.get(i);
position(buf, i + 1, 4);
// Last line has special handling. We want to position the error in the middle
// of the line, so for extremely long lines we need to shift things over.
if (i + 1 == size) {
int errlen = pos[1] - pos[0];
if (errlen > WINDOW_SIZE) {
// Location of the error
int errpos = pos[0] + erridx;
int skip = (int)Math.floor(WINDOW_SIZE / 2.0);
int leftpos = Math.max(errpos - skip, pos[0]);
erridx -= leftpos - pos[0] - 4;
buf.append("... ");
int min = Math.min(leftpos + WINDOW_SIZE, pos[1]);
String part = raw.substring(leftpos, min);
buf.append(part);
} else {
buf.append(raw.substring(pos[0], pos[1]));
}
} else {
buf.append(compressString(raw.substring(pos[0], pos[1])));
}
}
// Position an arrow at the offending character position
if (buf.prevChar() != '\n') {
buf.append('\n');
}
indent(buf, 7);
for (int i = 0; i < erridx; i++) {
buf.append('.');
}
buf.append("^\n");
ParseError error = new ParseError();
error.filePath(filePath);
error.errorMessage(buf.toString());
exc.push(error);
return exc;
}
private static void indent(Buffer buf, int width) {
for (int i = 0; i < width; i++) {
buf.append(' ');
}
buf.indent();
}
private static int position(Buffer buf, int line, int colWidth) {
String pos = Integer.toString(line);
int width = colWidth - pos.length();
for (int i = 0; i < width; i++) {
buf.append(' ');
}
buf.append(pos).append(" ");
return pos.length();
}
public static String compressString(String value) {
int len = value.length();
if (len <= WINDOW_SIZE) {
return value;
}
/* Calculate the number of visible characters and maximum segment size then:
* (a) if the segment size is <= 10 chars, place the ellipses at the end
* (b) otherwise, place the ellipses in the middle
. */
StringBuilder buf = new StringBuilder(WINDOW_SIZE);
int visible = WINDOW_SIZE - 4;
int segSize = (int)Math.floor(visible / 2.0);
return buf.append(value.substring(0, visible - segSize))
.append(" ... ")
.append(value.substring(len - segSize, len))
.toString();
}
}