Skip to content

Commit 5b378cf

Browse files
authored
Handle CRLF in multi line attributes (#148)
1 parent 155d388 commit 5b378cf

File tree

8 files changed

+53
-27
lines changed

8 files changed

+53
-27
lines changed

packages/analyzer/src/analyzer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,8 +154,8 @@ export class Analyzer {
154154

155155
export function parserErrorToAnalyzerIssue(error: ParserError): AnalyzerIssue {
156156
return {
157-
start: error.range.start,
158-
end: error.range.end,
157+
start: error.start,
158+
end: error.end,
159159
message: error.description,
160160
name: "knuckles/parser",
161161
severity: AnalyzerSeverity.Error,

packages/language-server/src/features/diagnostics.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,10 @@ function translateIssueToDiagnostic(
3535
const start = issue.start ?? Position.fromOffset(0, text);
3636
const end = issue.end ?? Position.fromOffset(start.offset + 1, text);
3737
const range = vscode.Range.create(
38-
vscode.Position.create(start.line, start.column),
39-
vscode.Position.create(end.line, end.column),
38+
start.line,
39+
start.column,
40+
end.line,
41+
end.column,
4042
);
4143
const severity = {
4244
[AnalyzerSeverity.Error]: vscode.DiagnosticSeverity.Error,

packages/location/src/position.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,12 @@ describe("Position", () => {
3838
offset: 6,
3939
});
4040
});
41+
42+
it("Converts offset in middle of CRLF to line and column", () => {
43+
const position = Position.fromOffset(9, "foo\r\nbar\r\n");
44+
expect(position).toMatchObject({
45+
line: 2,
46+
column: 0,
47+
});
48+
});
4149
});

packages/location/src/position.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export default class Position {
1919
if (nl) {
2020
for (const s of text.split(nl)) {
2121
if (i - s.length <= 0) {
22-
column = i;
22+
column = Math.max(i, 0);
2323
i = 0;
2424
break;
2525
} else {

packages/parser/src/error.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import type { Range } from "@knuckles/location";
1+
import type { Position } from "@knuckles/location";
22

33
export class ParserError extends Error {
44
constructor(
5-
readonly range: Range,
5+
readonly start: Position,
6+
readonly end: Position | undefined,
67
readonly description: string,
78
) {
8-
super(`${range.start.format()}: ${description}`);
9+
super(`${start.format()}: ${description}`);
910
}
1011
}

packages/parser/src/parser.ts

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,12 @@ export default class Parser {
5050
}
5151

5252
//#region Utils
53-
#error(range: Range, description: string) {
54-
const error = new ParserError(range, description);
53+
#error(range: Range, description: string): ParserError;
54+
#error(start: Position, description: string): ParserError;
55+
#error(location: Range | Position, description: string): ParserError {
56+
const start = location instanceof Range ? location.start : location;
57+
const end = location instanceof Range ? location.end : undefined;
58+
const error = new ParserError(start, end, description);
5559
this.errors.push(error);
5660
return error;
5761
}
@@ -391,6 +395,17 @@ export default class Parser {
391395
const range = parse5LocationToRange(
392396
node.sourceCodeLocation!.attrs![attr.name]!,
393397
);
398+
const valueRange = new Range(
399+
Position.fromOffset(
400+
range.start.offset +
401+
// name
402+
attr.name.length +
403+
// equal
404+
1,
405+
this.#string,
406+
),
407+
range.end,
408+
);
394409

395410
return new Attribute({
396411
name: new Identifier({
@@ -404,19 +419,13 @@ export default class Parser {
404419
),
405420
}),
406421
value: new StringLiteral({
407-
value: attr.value,
408-
quote,
409-
range: new Range(
410-
Position.fromOffset(
411-
range.start.offset +
412-
// name
413-
attr.name.length +
414-
// equal
415-
1,
416-
this.#string,
417-
),
418-
range.end,
422+
// parse5 replaces all occurrences of CRLF with LF, which breaks mappings.
423+
value: this.#string.slice(
424+
valueRange.start.offset + (quoted ? 1 : 0),
425+
valueRange.end.offset - (quoted ? 1 : 0),
419426
),
427+
quote,
428+
range: valueRange,
420429
}),
421430
namespace: attr.namespace,
422431
prefix: attr.prefix,

packages/parser/src/utils/char-iter.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ParserError } from "../error.js";
2-
import { Range } from "@knuckles/location";
2+
import { Position } from "@knuckles/location";
33

44
export default class CharIter {
55
#index: number;
@@ -31,7 +31,8 @@ export default class CharIter {
3131
next() {
3232
if (this.#index >= this.chars.length - 1) {
3333
throw new ParserError(
34-
Range.fromOffsets(this.#index, this.#index + 1, this.string),
34+
Position.fromOffset(this.#index, this.string),
35+
undefined,
3536
"Unexpected end of input.",
3637
);
3738
} else {
@@ -53,7 +54,8 @@ export default class CharIter {
5354
for (const char of chars) {
5455
if (this.char() !== char) {
5556
throw new ParserError(
56-
Range.fromOffsets(start, start + string.length, this.string),
57+
Position.fromOffset(start, this.string),
58+
Position.fromOffset(start + string.length, this.string),
5759
`Expected "${string}".`,
5860
);
5961
}

packages/ssr/src/renderer.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import type {
1414
ModuleProviderResolveResult,
1515
} from "./module-provider.js";
1616
import type { Plugin, Self, Sibling } from "./plugin.js";
17-
import { type Range } from "@knuckles/location";
17+
import { Position, Range } from "@knuckles/location";
1818
import { parse } from "@knuckles/parser";
1919
import {
2020
Element,
@@ -222,7 +222,11 @@ export class Renderer {
222222
createDiagnostic({
223223
type: "error",
224224
message: error.description,
225-
range: error.range,
225+
range: new Range(
226+
error.start,
227+
error.end ??
228+
Position.translate(error.start, 1, this.modified.original),
229+
),
226230
cause: error,
227231
}),
228232
),

0 commit comments

Comments
 (0)