Skip to content

Commit

Permalink
test: add scope test (#219)
Browse files Browse the repository at this point in the history
  • Loading branch information
ota-meshi committed Jan 22, 2024
1 parent 7b1f6a1 commit 18e8e17
Show file tree
Hide file tree
Showing 3 changed files with 165 additions and 172 deletions.
144 changes: 6 additions & 138 deletions scripts/update-fixtures-ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand Down Expand Up @@ -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
//------------------------------------------------------------------------------
Expand Down
53 changes: 19 additions & 34 deletions test/ast.js
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
}

/**
Expand Down Expand Up @@ -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)
})
Expand Down
140 changes: 140 additions & 0 deletions test/test-utils.js
Original file line number Diff line number Diff line change
@@ -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))
)
}
}

0 comments on commit 18e8e17

Please sign in to comment.