diff --git a/src/tokenizer/__tests__/__input__/parsing-error-no-open-end.html b/src/tokenizer/__tests__/__input__/parsing-error-no-open-end.html new file mode 100644 index 0000000..fdb9008 --- /dev/null +++ b/src/tokenizer/__tests__/__input__/parsing-error-no-open-end.html @@ -0,0 +1,4 @@ + + + diff --git a/src/tokenizer/__tests__/tokenize.spec.ts b/src/tokenizer/__tests__/tokenize.spec.ts index b6d4454..853a8bb 100644 --- a/src/tokenizer/__tests__/tokenize.spec.ts +++ b/src/tokenizer/__tests__/tokenize.spec.ts @@ -193,4 +193,16 @@ describe("tokenize", () => { expect(tokens).toEqual(output); } ); + + it("parsing error", () => { + const html = fs.readFileSync( + path.join(__dirname, "__input__", "parsing-error-no-open-end.html"), + "utf-8" + ); + expect(() => + tokenize(html, defaultTokenAdapter) + ).toThrowErrorMatchingInlineSnapshot( + `"Unexpected end of tag. Expected '>' to close the opening tag. (2:2)"` + ); + }); }); diff --git a/src/tokenizer/handlers/attributes.ts b/src/tokenizer/handlers/attributes.ts index be7198c..0ed9b1e 100644 --- a/src/tokenizer/handlers/attributes.ts +++ b/src/tokenizer/handlers/attributes.ts @@ -1,5 +1,5 @@ import { TokenizerContextTypes, TokenTypes } from "../../constants"; -import { calculateTokenPosition, isWhitespace } from "../../utils"; +import { calculateTokenPosition, isWhitespace, raise } from "../../utils"; import type { TokenizerState } from "../../types"; import { CharsBuffer } from "../chars-buffer"; @@ -33,6 +33,12 @@ function parseTagEnd(state: TokenizerState) { } function parseNoneWhitespace(chars: CharsBuffer, state: TokenizerState) { + if (chars.value() === "<") { + raise( + calculateTokenPosition(state, { keepBuffer: true }), + "Unexpected end of tag. Expected '>' to close the opening tag." + ); + } state.accumulatedContent.replace(state.decisionBuffer); state.currentContext = TokenizerContextTypes.AttributeKey; state.sourceCode.next(); diff --git a/src/utils/index.ts b/src/utils/index.ts index ab14709..3529556 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -14,3 +14,4 @@ export * from "./first"; export * from "./clone-location"; export * from "./init-if-none"; export * from "./templates"; +export * from "./raise"; diff --git a/src/utils/raise.ts b/src/utils/raise.ts new file mode 100644 index 0000000..2effbcb --- /dev/null +++ b/src/utils/raise.ts @@ -0,0 +1,23 @@ +import { Range } from "../types"; +import { SourceLocation } from "../types/source-location"; + +export function raise( + position: { + loc: SourceLocation; + range: Range; + }, + message: string +): never { + const errorMessage = + message + + " (" + + position.loc.start.line + + ":" + + position.loc.start.column + + ")"; + const error: any = new SyntaxError(errorMessage); + error.pos = position.range[0]; + error.loc = position.loc; + error.raiseAt = position.range[0]; + throw error; +}