diff --git a/src/parser/converts/attr.ts b/src/parser/converts/attr.ts index 1eaf8f69..912eb4fc 100644 --- a/src/parser/converts/attr.ts +++ b/src/parser/converts/attr.ts @@ -381,7 +381,14 @@ function convertStyleDirective( directive.key, null, (expression) => { - directive.key.name = expression as ESTree.Identifier + if (expression.type !== "Identifier") { + throw new ParseError( + `Expected JS identifier or attribute value.`, + expression.range![0], + ctx, + ) + } + directive.key.name = expression }, ) return directive @@ -627,12 +634,26 @@ function processDirectiveExpression< getWithLoc(node.expression).end = keyName.range[1] } processors.processExpression(node.expression, shorthand).push((es) => { + if (node.expression && es.type !== node.expression.type) { + throw new ParseError( + `Expected ${node.expression.type}, but ${es.type} found.`, + es.range![0], + ctx, + ) + } directive.expression = es }) } if (!shorthand) { if (processors.processName) { processors.processName(keyName).push((es) => { + if (es.type !== "Identifier") { + throw new ParseError( + `Expected JS identifier.`, + es.range![0], + ctx, + ) + } key.name = es }) } else { diff --git a/tests/fixtures/parser/ast/illegal/dot-in-bind02-no-undef-result.json b/tests/fixtures/parser/ast/illegal/dot-in-bind02-no-undef-result.json deleted file mode 100644 index a582b56c..00000000 --- a/tests/fixtures/parser/ast/illegal/dot-in-bind02-no-undef-result.json +++ /dev/null @@ -1,8 +0,0 @@ -[ - { - "ruleId": "no-undef", - "code": "va", - "line": 3, - "column": 13 - } -] \ No newline at end of file diff --git a/tests/fixtures/parser/ast/illegal/dot-in-bind02-output.json b/tests/fixtures/parser/ast/illegal/dot-in-bind02-output.json deleted file mode 100644 index ad8a0d2e..00000000 --- a/tests/fixtures/parser/ast/illegal/dot-in-bind02-output.json +++ /dev/null @@ -1,587 +0,0 @@ -{ - "type": "Program", - "body": [ - { - "type": "SvelteScriptElement", - "name": { - "type": "SvelteName", - "name": "script", - "range": [ - 1, - 7 - ], - "loc": { - "start": { - "line": 1, - "column": 1 - }, - "end": { - "line": 1, - "column": 7 - } - } - }, - "startTag": { - "type": "SvelteStartTag", - "attributes": [], - "selfClosing": false, - "range": [ - 0, - 8 - ], - "loc": { - "start": { - "line": 1, - "column": 0 - }, - "end": { - "line": 1, - "column": 8 - } - } - }, - "body": [], - "endTag": { - "type": "SvelteEndTag", - "range": [ - 9, - 18 - ], - "loc": { - "start": { - "line": 2, - "column": 0 - }, - "end": { - "line": 2, - "column": 9 - } - } - }, - "range": [ - 0, - 18 - ], - "loc": { - "start": { - "line": 1, - "column": 0 - }, - "end": { - "line": 2, - "column": 9 - } - } - }, - { - "type": "SvelteText", - "value": "\n", - "range": [ - 18, - 19 - ], - "loc": { - "start": { - "line": 2, - "column": 9 - }, - "end": { - "line": 3, - "column": 0 - } - } - }, - { - "type": "SvelteElement", - "kind": "html", - "name": { - "type": "SvelteName", - "name": "input", - "range": [ - 20, - 25 - ], - "loc": { - "start": { - "line": 3, - "column": 1 - }, - "end": { - "line": 3, - "column": 6 - } - } - }, - "startTag": { - "type": "SvelteStartTag", - "attributes": [ - { - "type": "SvelteDirective", - "kind": "Binding", - "key": { - "type": "SvelteDirectiveKey", - "name": { - "type": "SvelteName", - "name": "va.lue", - "range": [ - 31, - 37 - ], - "loc": { - "start": { - "line": 3, - "column": 12 - }, - "end": { - "line": 3, - "column": 18 - } - } - }, - "modifiers": [], - "range": [ - 26, - 37 - ], - "loc": { - "start": { - "line": 3, - "column": 7 - }, - "end": { - "line": 3, - "column": 18 - } - } - }, - "expression": { - "type": "MemberExpression", - "computed": false, - "object": { - "type": "Identifier", - "name": "va", - "range": [ - 31, - 33 - ], - "loc": { - "start": { - "line": 3, - "column": 12 - }, - "end": { - "line": 3, - "column": 14 - } - } - }, - "optional": false, - "property": { - "type": "Identifier", - "name": "lue", - "range": [ - 34, - 37 - ], - "loc": { - "start": { - "line": 3, - "column": 15 - }, - "end": { - "line": 3, - "column": 18 - } - } - }, - "range": [ - 31, - 37 - ], - "loc": { - "start": { - "line": 3, - "column": 12 - }, - "end": { - "line": 3, - "column": 18 - } - } - }, - "shorthand": true, - "range": [ - 26, - 37 - ], - "loc": { - "start": { - "line": 3, - "column": 7 - }, - "end": { - "line": 3, - "column": 18 - } - } - } - ], - "selfClosing": true, - "range": [ - 19, - 39 - ], - "loc": { - "start": { - "line": 3, - "column": 0 - }, - "end": { - "line": 3, - "column": 20 - } - } - }, - "children": [], - "endTag": null, - "range": [ - 19, - 39 - ], - "loc": { - "start": { - "line": 3, - "column": 0 - }, - "end": { - "line": 3, - "column": 20 - } - } - } - ], - "sourceType": "module", - "comments": [], - "tokens": [ - { - "type": "Punctuator", - "value": "<", - "range": [ - 0, - 1 - ], - "loc": { - "start": { - "line": 1, - "column": 0 - }, - "end": { - "line": 1, - "column": 1 - } - } - }, - { - "type": "HTMLIdentifier", - "value": "script", - "range": [ - 1, - 7 - ], - "loc": { - "start": { - "line": 1, - "column": 1 - }, - "end": { - "line": 1, - "column": 7 - } - } - }, - { - "type": "Punctuator", - "value": ">", - "range": [ - 7, - 8 - ], - "loc": { - "start": { - "line": 1, - "column": 7 - }, - "end": { - "line": 1, - "column": 8 - } - } - }, - { - "type": "Punctuator", - "value": "<", - "range": [ - 9, - 10 - ], - "loc": { - "start": { - "line": 2, - "column": 0 - }, - "end": { - "line": 2, - "column": 1 - } - } - }, - { - "type": "Punctuator", - "value": "/", - "range": [ - 10, - 11 - ], - "loc": { - "start": { - "line": 2, - "column": 1 - }, - "end": { - "line": 2, - "column": 2 - } - } - }, - { - "type": "HTMLIdentifier", - "value": "script", - "range": [ - 11, - 17 - ], - "loc": { - "start": { - "line": 2, - "column": 2 - }, - "end": { - "line": 2, - "column": 8 - } - } - }, - { - "type": "Punctuator", - "value": ">", - "range": [ - 17, - 18 - ], - "loc": { - "start": { - "line": 2, - "column": 8 - }, - "end": { - "line": 2, - "column": 9 - } - } - }, - { - "type": "HTMLText", - "value": "\n", - "range": [ - 18, - 19 - ], - "loc": { - "start": { - "line": 2, - "column": 9 - }, - "end": { - "line": 3, - "column": 0 - } - } - }, - { - "type": "Punctuator", - "value": "<", - "range": [ - 19, - 20 - ], - "loc": { - "start": { - "line": 3, - "column": 0 - }, - "end": { - "line": 3, - "column": 1 - } - } - }, - { - "type": "HTMLIdentifier", - "value": "input", - "range": [ - 20, - 25 - ], - "loc": { - "start": { - "line": 3, - "column": 1 - }, - "end": { - "line": 3, - "column": 6 - } - } - }, - { - "type": "HTMLIdentifier", - "value": "bind", - "range": [ - 26, - 30 - ], - "loc": { - "start": { - "line": 3, - "column": 7 - }, - "end": { - "line": 3, - "column": 11 - } - } - }, - { - "type": "Punctuator", - "value": ":", - "range": [ - 30, - 31 - ], - "loc": { - "start": { - "line": 3, - "column": 11 - }, - "end": { - "line": 3, - "column": 12 - } - } - }, - { - "type": "Identifier", - "value": "va", - "range": [ - 31, - 33 - ], - "loc": { - "start": { - "line": 3, - "column": 12 - }, - "end": { - "line": 3, - "column": 14 - } - } - }, - { - "type": "Punctuator", - "value": ".", - "range": [ - 33, - 34 - ], - "loc": { - "start": { - "line": 3, - "column": 14 - }, - "end": { - "line": 3, - "column": 15 - } - } - }, - { - "type": "Identifier", - "value": "lue", - "range": [ - 34, - 37 - ], - "loc": { - "start": { - "line": 3, - "column": 15 - }, - "end": { - "line": 3, - "column": 18 - } - } - }, - { - "type": "Punctuator", - "value": "/", - "range": [ - 37, - 38 - ], - "loc": { - "start": { - "line": 3, - "column": 18 - }, - "end": { - "line": 3, - "column": 19 - } - } - }, - { - "type": "Punctuator", - "value": ">", - "range": [ - 38, - 39 - ], - "loc": { - "start": { - "line": 3, - "column": 19 - }, - "end": { - "line": 3, - "column": 20 - } - } - } - ], - "range": [ - 0, - 40 - ], - "loc": { - "start": { - "line": 1, - "column": 0 - }, - "end": { - "line": 4, - "column": 0 - } - } -} \ No newline at end of file diff --git a/tests/fixtures/parser/ast/illegal/dot-in-bind02-scope-output.json b/tests/fixtures/parser/ast/illegal/dot-in-bind02-scope-output.json deleted file mode 100644 index ac8842cd..00000000 --- a/tests/fixtures/parser/ast/illegal/dot-in-bind02-scope-output.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "type": "global", - "variables": [ - { - "name": "$$slots", - "identifiers": [], - "defs": [], - "references": [] - }, - { - "name": "$$props", - "identifiers": [], - "defs": [], - "references": [] - }, - { - "name": "$$restProps", - "identifiers": [], - "defs": [], - "references": [] - } - ], - "references": [], - "childScopes": [ - { - "type": "module", - "variables": [], - "references": [ - { - "identifier": { - "type": "Identifier", - "name": "va", - "range": [ - 31, - 33 - ], - "loc": { - "start": { - "line": 3, - "column": 12 - }, - "end": { - "line": 3, - "column": 14 - } - } - }, - "from": "module", - "init": null, - "resolved": null - } - ], - "childScopes": [], - "through": [ - { - "identifier": { - "type": "Identifier", - "name": "va", - "range": [ - 31, - 33 - ], - "loc": { - "start": { - "line": 3, - "column": 12 - }, - "end": { - "line": 3, - "column": 14 - } - } - }, - "from": "module", - "init": null, - "resolved": null - } - ] - } - ], - "through": [ - { - "identifier": { - "type": "Identifier", - "name": "va", - "range": [ - 31, - 33 - ], - "loc": { - "start": { - "line": 3, - "column": 12 - }, - "end": { - "line": 3, - "column": 14 - } - } - }, - "from": "module", - "init": null, - "resolved": null - } - ] -} \ No newline at end of file diff --git a/tests/fixtures/parser/ast/illegal/dot-in-bind02-input.svelte b/tests/fixtures/parser/error/dot-in-bind02-input.svelte similarity index 100% rename from tests/fixtures/parser/ast/illegal/dot-in-bind02-input.svelte rename to tests/fixtures/parser/error/dot-in-bind02-input.svelte diff --git a/tests/fixtures/parser/error/dot-in-bind02-output.json b/tests/fixtures/parser/error/dot-in-bind02-output.json new file mode 100644 index 00000000..5e4b6c73 --- /dev/null +++ b/tests/fixtures/parser/error/dot-in-bind02-output.json @@ -0,0 +1,6 @@ +{ + "index": 31, + "lineNumber": 3, + "message": "Expected Identifier, but MemberExpression found.", + "column": 12 +} \ No newline at end of file diff --git a/tests/fixtures/parser/error/name01-input.svelte b/tests/fixtures/parser/error/name01-input.svelte new file mode 100644 index 00000000..296cdd7f --- /dev/null +++ b/tests/fixtures/parser/error/name01-input.svelte @@ -0,0 +1,7 @@ + + +

Hello {name}!

diff --git a/tests/fixtures/parser/error/name01-output.json b/tests/fixtures/parser/error/name01-output.json new file mode 100644 index 00000000..44421403 --- /dev/null +++ b/tests/fixtures/parser/error/name01-output.json @@ -0,0 +1,6 @@ +{ + "index": 85, + "lineNumber": 7, + "message": "Expected JS identifier.", + "column": 15 +} \ No newline at end of file diff --git a/tests/fixtures/parser/error/shorthand01-input.svelte b/tests/fixtures/parser/error/shorthand01-input.svelte new file mode 100644 index 00000000..30b862c7 --- /dev/null +++ b/tests/fixtures/parser/error/shorthand01-input.svelte @@ -0,0 +1,8 @@ + + +Hello {name}! \ No newline at end of file diff --git a/tests/fixtures/parser/error/shorthand01-output.json b/tests/fixtures/parser/error/shorthand01-output.json new file mode 100644 index 00000000..9a1fc995 --- /dev/null +++ b/tests/fixtures/parser/error/shorthand01-output.json @@ -0,0 +1,6 @@ +{ + "index": 129, + "lineNumber": 8, + "message": "Expected Identifier, but BinaryExpression found.", + "column": 16 +} \ No newline at end of file diff --git a/tests/fixtures/parser/error/shorthand02-input.svelte b/tests/fixtures/parser/error/shorthand02-input.svelte new file mode 100644 index 00000000..b27bcf3c --- /dev/null +++ b/tests/fixtures/parser/error/shorthand02-input.svelte @@ -0,0 +1,7 @@ + + +

Hello {name}!

diff --git a/tests/fixtures/parser/error/shorthand02-output.json b/tests/fixtures/parser/error/shorthand02-output.json new file mode 100644 index 00000000..8a94e47b --- /dev/null +++ b/tests/fixtures/parser/error/shorthand02-output.json @@ -0,0 +1,6 @@ +{ + "index": 80, + "lineNumber": 7, + "message": "Expected JS identifier or attribute value.", + "column": 10 +} \ No newline at end of file diff --git a/tests/src/parser/error.ts b/tests/src/parser/error.ts new file mode 100644 index 00000000..dcd4ff76 --- /dev/null +++ b/tests/src/parser/error.ts @@ -0,0 +1,52 @@ +import assert from "assert" +import fs from "fs" +import { parseForESLint } from "../../../src" +import { + BASIC_PARSER_OPTIONS, + listupFixtures, + nodeReplacer, + normalizeError, +} from "./test-utils" +import path from "path" + +const ERROR_FIXTURE_ROOT = path.resolve( + __dirname, + "../../fixtures/parser/error", +) + +function parse(code: string, filePath: string) { + return parseForESLint(code, { + ...BASIC_PARSER_OPTIONS!, + filePath, + }) +} + +describe("Check for Error.", () => { + for (const { + input, + inputFileName, + outputFileName, + meetRequirements, + } of listupFixtures(ERROR_FIXTURE_ROOT)) { + describe(inputFileName, () => { + if (!meetRequirements("test")) { + return + } + it("most to the expected error.", () => { + try { + parse(input, inputFileName) + } catch (e) { + const errorJson = JSON.stringify( + normalizeError(e), + nodeReplacer, + 2, + ) + const output = fs.readFileSync(outputFileName, "utf8") + assert.strictEqual(errorJson, output) + return + } + assert.fail("Expected error") + }) + }) + } +}) diff --git a/tests/src/parser/parser.ts b/tests/src/parser/parser.ts index 4210119d..e9c860ba 100644 --- a/tests/src/parser/parser.ts +++ b/tests/src/parser/parser.ts @@ -26,7 +26,7 @@ describe("Check for AST.", () => { inputFileName, outputFileName, scopeFileName, - requirements, + meetRequirements, } of listupFixtures()) { describe(inputFileName, () => { let result: any @@ -37,7 +37,7 @@ describe("Check for AST.", () => { const output = fs.readFileSync(outputFileName, "utf8") assert.strictEqual(astJson, output) }) - if (canTest(requirements, "scope")) + if (meetRequirements("scope")) it("most to generate the expected scope.", () => { let json: any = scopeToJSON(result.scopeManager) let output: any = fs.readFileSync(scopeFileName, "utf8") @@ -76,25 +76,6 @@ describe("Check for AST.", () => { } }) -function canTest( - requirements: { scope?: Record }, - key: "scope", -) { - const obj = requirements[key] - if (obj) { - if ( - Object.entries(obj).some(([pkgName, pkgVersion]) => { - // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires -- ignore - const pkg = require(`${pkgName}/package.json`) - return !semver.satisfies(pkg.version, pkgVersion) - }) - ) { - return false - } - } - return true -} - function checkTokens(ast: SvelteProgram, input: string) { const allTokens = [...ast.tokens, ...ast.comments].sort( (a, b) => a.range[0] - b.range[0], diff --git a/tests/src/parser/test-utils.ts b/tests/src/parser/test-utils.ts index cadcd854..465c6e0a 100644 --- a/tests/src/parser/test-utils.ts +++ b/tests/src/parser/test-utils.ts @@ -1,5 +1,7 @@ +/* global require -- node */ import path from "path" import fs from "fs" +import semver from "semver" import type { Linter, Scope as ESLintScope } from "eslint" import { LinesAndColumns } from "../../../src/context" import type { Reference, Scope, ScopeManager, Variable } from "eslint-scope" @@ -16,7 +18,7 @@ export const BASIC_PARSER_OPTIONS: Linter.BaseConfig["parser project: require.resolve("../../fixtures/parser/tsconfig.test.json"), extraFileExtensions: [".svelte"], } -export function* listupFixtures(): IterableIterator<{ +export function* listupFixtures(dir?: string): IterableIterator<{ input: string inputFileName: string outputFileName: string @@ -26,8 +28,9 @@ export function* listupFixtures(): IterableIterator<{ scope?: Record } getRuleOutputFileName: (ruleName: string) => string + meetRequirements: (key: "test" | "scope") => boolean }> { - yield* listupFixturesImpl(AST_FIXTURE_ROOT) + yield* listupFixturesImpl(dir || AST_FIXTURE_ROOT) } function* listupFixturesImpl(dir: string): IterableIterator<{ @@ -40,6 +43,7 @@ function* listupFixturesImpl(dir: string): IterableIterator<{ scope?: Record } getRuleOutputFileName: (ruleName: string) => string + meetRequirements: (key: "test" | "scope") => boolean }> { for (const filename of fs.readdirSync(dir)) { const inputFileName = path.join(dir, filename) @@ -62,21 +66,42 @@ function* listupFixturesImpl(dir: string): IterableIterator<{ ) const input = fs.readFileSync(inputFileName, "utf8") + const requirements = fs.existsSync(requirementsFileName) + ? JSON.parse(fs.readFileSync(requirementsFileName, "utf-8")) + : {} yield { input, inputFileName, outputFileName, scopeFileName, typeFileName: fs.existsSync(typeFileName) ? typeFileName : null, - requirements: fs.existsSync(requirementsFileName) - ? JSON.parse(fs.readFileSync(requirementsFileName, "utf-8")) - : {}, + requirements, getRuleOutputFileName: (ruleName) => { return inputFileName.replace( /input\.svelte$/u, `${ruleName}-result.json`, ) }, + meetRequirements(key) { + const obj = requirements[key] + if (obj) { + if ( + Object.entries(obj).some( + ([pkgName, pkgVersion]) => { + // eslint-disable-next-line @typescript-eslint/no-require-imports, @typescript-eslint/no-var-requires -- ignore + const pkg = require(`${pkgName}/package.json`) + return !semver.satisfies( + pkg.version, + pkgVersion as string, + ) + }, + ) + ) { + return false + } + } + return true + }, } } if ( @@ -170,6 +195,15 @@ function normalizeDef(reference: ESLintScope.Definition) { } } +export function normalizeError(error: any): any { + return { + message: error.message, + index: error.index, + lineNumber: error.lineNumber, + column: error.column, + } +} + /** * Remove `parent` properties from the given AST. */ diff --git a/tools/update-fixtures.ts b/tools/update-fixtures.ts index 842bc75a..306b0194 100644 --- a/tools/update-fixtures.ts +++ b/tools/update-fixtures.ts @@ -1,4 +1,5 @@ import fs from "fs" +import path from "path" import { Linter } from "eslint" import * as parser from "../src/index" import { parseForESLint } from "../src/parser" @@ -7,11 +8,17 @@ import { getMessageData, listupFixtures, nodeReplacer, + normalizeError, scopeToJSON, } from "../tests/src/parser/test-utils" import type ts from "typescript" import type ESTree from "estree" +const ERROR_FIXTURE_ROOT = path.resolve( + __dirname, + "../tests/fixtures/parser/error", +) + const RULES = [ "no-unused-labels", "no-extra-label", @@ -37,6 +44,19 @@ function parse(code: string, filePath: string) { }) } +for (const { input, inputFileName, outputFileName } of listupFixtures( + ERROR_FIXTURE_ROOT, +)) { + // eslint-disable-next-line no-console -- ignore + console.log(inputFileName) + try { + parse(input, inputFileName) + } catch (e) { + const errorJson = JSON.stringify(normalizeError(e), nodeReplacer, 2) + fs.writeFileSync(outputFileName, errorJson, "utf8") + } +} + for (const { input, inputFileName,