diff --git a/scripts/update-fixtures-ast.js b/scripts/update-fixtures-ast.js index 8990574..cb32843 100644 --- a/scripts/update-fixtures-ast.js +++ b/scripts/update-fixtures-ast.js @@ -12,8 +12,13 @@ const fs = require("fs") const path = require("path") const parser = require("../src") -const escope = require("eslint-scope") const semver = require("semver") +const { + scopeToJSON, + analyze, + replacer, + getAllTokens, +} = require("../test/test-utils") //------------------------------------------------------------------------------ // Helpers @@ -30,40 +35,6 @@ const PARSER_OPTIONS = { eslintScopeManager: true, } -/** - * Remove `parent` proeprties from the given AST. - * @param {string} key The key. - * @param {any} value The value of the key. - * @returns {any} The value of the key to output. - */ -function replacer(key, value) { - if (key === "parent") { - return undefined - } - if (key === "errors" && Array.isArray(value)) { - return value.map((e) => ({ - message: e.message, - index: e.index, - lineNumber: e.lineNumber, - column: e.column, - })) - } - return value -} - -/** - * Get all tokens of the given AST. - * @param {ASTNode} ast The root node of AST. - * @returns {Token[]} Tokens. - */ -function getAllTokens(ast) { - const tokenArrays = [ast.tokens, ast.comments] - if (ast.templateBody != null) { - tokenArrays.push(ast.templateBody.tokens, ast.templateBody.comments) - } - return Array.prototype.concat.apply([], tokenArrays) -} - /** * Create simple tree. * @param {string} source The source code. @@ -98,109 +69,6 @@ function getTree(source, ast) { return root.children } -function scopeToJSON(scopeManager) { - return JSON.stringify(normalizeScope(scopeManager.globalScope), replacer, 4) - - function normalizeScope(scope) { - return { - type: scope.type, - variables: scope.variables.map(normalizeVar), - references: scope.references.map(normalizeReference), - childScopes: scope.childScopes.map(normalizeScope), - through: scope.through.map(normalizeReference), - } - } - - function normalizeVar(v) { - return { - name: v.name, - identifiers: v.identifiers.map(normalizeId), - defs: v.defs.map(normalizeDef), - references: v.references.map(normalizeReference), - } - } - - function normalizeReference(reference) { - return { - identifier: normalizeId(reference.identifier), - from: reference.from.type, - resolved: normalizeId( - reference.resolved && - reference.resolved.defs && - reference.resolved.defs[0] && - reference.resolved.defs[0].name, - ), - init: reference.init || null, - vueUsedInTemplate: reference.vueUsedInTemplate - ? reference.vueUsedInTemplate - : undefined, - } - } - - function normalizeDef(def) { - return { - type: def.type, - node: normalizeDefNode(def.node), - name: def.name.name, - } - } - - function normalizeId(identifier) { - return ( - identifier && { - type: identifier.type, - name: identifier.name, - loc: identifier.loc, - } - ) - } - - function normalizeDefNode(node) { - return { - type: node.type, - loc: node.loc, - } - } -} - -/** - * Analyze scope - */ -function analyze(ast, parserOptions) { - const ecmaVersion = parserOptions.ecmaVersion || 2017 - const ecmaFeatures = parserOptions.ecmaFeatures || {} - const sourceType = parserOptions.sourceType || "script" - const result = escope.analyze(ast, { - ignoreEval: true, - nodejsScope: false, - impliedStrict: ecmaFeatures.impliedStrict, - ecmaVersion, - sourceType, - fallback: getFallbackKeys, - }) - - return result - - function getFallbackKeys(node) { - return Object.keys(node).filter(fallbackKeysFilter, node) - } - - function fallbackKeysFilter(key) { - const value = null - return ( - key !== "comments" && - key !== "leadingComments" && - key !== "loc" && - key !== "parent" && - key !== "range" && - key !== "tokens" && - key !== "trailingComments" && - typeof value === "object" && - (typeof value.type === "string" || Array.isArray(value)) - ) - } -} - //------------------------------------------------------------------------------ // Main //------------------------------------------------------------------------------ diff --git a/test/ast.js b/test/ast.js index c5e946a..31bc8e3 100644 --- a/test/ast.js +++ b/test/ast.js @@ -16,6 +16,7 @@ const lodash = require("lodash") const parser = require("../src") const Linter = require("./fixtures/eslint").Linter const semver = require("semver") +const { scopeToJSON, analyze, replacer, getAllTokens } = require("./test-utils") //------------------------------------------------------------------------------ // Helpers @@ -30,40 +31,7 @@ const PARSER_OPTIONS = { loc: true, range: true, tokens: true, -} - -/** - * Remove `parent` proeprties from the given AST. - * @param {string} key The key. - * @param {any} value The value of the key. - * @returns {any} The value of the key to output. - */ -function replacer(key, value) { - if (key === "parent") { - return undefined - } - if (key === "errors" && Array.isArray(value)) { - return value.map((e) => ({ - message: e.message, - index: e.index, - lineNumber: e.lineNumber, - column: e.column, - })) - } - return value -} - -/** - * Get all tokens of the given AST. - * @param {ASTNode} ast The root node of AST. - * @returns {Token[]} Tokens. - */ -function getAllTokens(ast) { - const tokenArrays = [ast.tokens, ast.comments] - if (ast.templateBody != null) { - tokenArrays.push(ast.templateBody.tokens, ast.templateBody.comments) - } - return Array.prototype.concat.apply([], tokenArrays) + eslintScopeManager: true, } /** @@ -305,6 +273,23 @@ describe("Template AST", () => { assert.strictEqual(actualText, expectedText) }) + it("should scope in the correct.", () => { + const version = require(`eslint/package.json`).version + if (!semver.satisfies(version, ">=8")) { + return + } + const resultPath = path.join(ROOT, `${name}/scope.json`) + if (!fs.existsSync(resultPath)) { + return + } + const expectedText = fs.readFileSync(resultPath, "utf8") + const actualText = scopeToJSON( + actual.scopeManager || analyze(actual.ast, options), + ) + + assert.strictEqual(actualText, expectedText) + }) + it("should have correct parent properties.", () => { validateParent(source, parserOptions) }) diff --git a/test/test-utils.js b/test/test-utils.js new file mode 100644 index 0000000..9373502 --- /dev/null +++ b/test/test-utils.js @@ -0,0 +1,140 @@ +const escope = require("eslint-scope") + +module.exports = { replacer, getAllTokens, scopeToJSON, analyze } + +/** + * Remove `parent` properties from the given AST. + * @param {string} key The key. + * @param {any} value The value of the key. + * @returns {any} The value of the key to output. + */ +function replacer(key, value) { + if (key === "parent") { + return undefined + } + if (key === "errors" && Array.isArray(value)) { + return value.map((e) => ({ + message: e.message, + index: e.index, + lineNumber: e.lineNumber, + column: e.column, + })) + } + return value +} + +/** + * Get all tokens of the given AST. + * @param {ASTNode} ast The root node of AST. + * @returns {Token[]} Tokens. + */ +function getAllTokens(ast) { + const tokenArrays = [ast.tokens, ast.comments] + if (ast.templateBody != null) { + tokenArrays.push(ast.templateBody.tokens, ast.templateBody.comments) + } + return Array.prototype.concat.apply([], tokenArrays) +} + +function scopeToJSON(scopeManager) { + return JSON.stringify(normalizeScope(scopeManager.globalScope), replacer, 4) + + function normalizeScope(scope) { + return { + type: scope.type, + variables: scope.variables.map(normalizeVar), + references: scope.references.map(normalizeReference), + childScopes: scope.childScopes.map(normalizeScope), + through: scope.through.map(normalizeReference), + } + } + + function normalizeVar(v) { + return { + name: v.name, + identifiers: v.identifiers.map(normalizeId), + defs: v.defs.map(normalizeDef), + references: v.references.map(normalizeReference), + } + } + + function normalizeReference(reference) { + return { + identifier: normalizeId(reference.identifier), + from: reference.from.type, + resolved: normalizeId( + reference.resolved && + reference.resolved.defs && + reference.resolved.defs[0] && + reference.resolved.defs[0].name, + ), + init: reference.init || null, + vueUsedInTemplate: reference.vueUsedInTemplate + ? reference.vueUsedInTemplate + : undefined, + } + } + + function normalizeDef(def) { + return { + type: def.type, + node: normalizeDefNode(def.node), + name: def.name.name, + } + } + + function normalizeId(identifier) { + return ( + identifier && { + type: identifier.type, + name: identifier.name, + loc: identifier.loc, + } + ) + } + + function normalizeDefNode(node) { + return { + type: node.type, + loc: node.loc, + } + } +} + +/** + * Analyze scope + */ +function analyze(ast, parserOptions) { + const ecmaVersion = parserOptions.ecmaVersion || 2017 + const ecmaFeatures = parserOptions.ecmaFeatures || {} + const sourceType = parserOptions.sourceType || "script" + const result = escope.analyze(ast, { + ignoreEval: true, + nodejsScope: false, + impliedStrict: ecmaFeatures.impliedStrict, + ecmaVersion, + sourceType, + fallback: getFallbackKeys, + }) + + return result + + function getFallbackKeys(node) { + return Object.keys(node).filter(fallbackKeysFilter, node) + } + + function fallbackKeysFilter(key) { + const value = null + return ( + key !== "comments" && + key !== "leadingComments" && + key !== "loc" && + key !== "parent" && + key !== "range" && + key !== "tokens" && + key !== "trailingComments" && + typeof value === "object" && + (typeof value.type === "string" || Array.isArray(value)) + ) + } +}