Skip to content

fix: context provided by defineCustomBlocksVisitor #211

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 56 additions & 44 deletions src/sfc/custom-block/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -213,26 +213,40 @@ export function createCustomBlockSharedContext({
parserOptions: any
}) {
let sourceCode: SourceCode
let scopeManager: ScopeManager
let currentNode: any
return {
serCurrentNode(node: any) {
currentNode = node
},
context: {
getAncestors: () => getAncestors(currentNode),

getAncestors: () => getSourceCode().getAncestors(currentNode),
getDeclaredVariables: (...args: any[]) =>
// @ts-expect-error -- ignore
getScopeManager().getDeclaredVariables(...args),
getScope: () => getScope(getScopeManager(), currentNode),
getSourceCode().getDeclaredVariables(...args),
getScope: () => getSourceCode().getScope(currentNode),
markVariableAsUsed: (name: string) =>
markVariableAsUsed(
getScopeManager(),
currentNode,
parserOptions,
name,
),
getSourceCode().markVariableAsUsed(name, currentNode),
get parserServices() {
return getSourceCode().parserServices
},
getSourceCode,
get sourceCode() {
return getSourceCode()
},
},
}

function getSourceCode(): SourceCode {
if (sourceCode) {
return sourceCode
}

const scopeManager = getScopeManager()

// eslint-disable-next-line @typescript-eslint/no-require-imports
const originalSourceCode = new (require("eslint").SourceCode)({
text,
ast: parsedResult.ast,
parserServices: {
customBlock,
parseCustomBlockElement(
Expand All @@ -251,44 +265,43 @@ export function createCustomBlockSharedContext({
? { parseError: parsedResult.error }
: {}),
},
getSourceCode,
get sourceCode() {
return getSourceCode()
},
},
}
scopeManager,
visitorKeys: parsedResult.visitorKeys,
})

function getSourceCode() {
return (
sourceCode ||
// eslint-disable-next-line @typescript-eslint/no-require-imports
(sourceCode = new (require("eslint").SourceCode)({
text,
ast: parsedResult.ast,
parserServices: parsedResult.services,
scopeManager: getScopeManager(),
visitorKeys: parsedResult.visitorKeys,
}))
)
const polyfills = {
markVariableAsUsed: (name: string, node: any) =>
markVariableAsUsed(scopeManager, node, parsedResult.ast, name),
getScope: (node: any) => getScope(scopeManager, node),
getAncestors: (node: any) => getAncestors(node),
getDeclaredVariables: (...args: any[]) =>
// @ts-expect-error -- ignore
scopeManager.getDeclaredVariables(...args),
}

return (sourceCode = new Proxy(originalSourceCode, {
get(_target, prop) {
return originalSourceCode[prop] || (polyfills as any)[prop]
},
}))
}

function getScopeManager() {
if (parsedResult.scopeManager || scopeManager) {
return parsedResult.scopeManager || scopeManager
if (parsedResult.scopeManager) {
return parsedResult.scopeManager
}

const ecmaVersion = getEcmaVersionIfUseEspree(parserOptions) || 2022
const ecmaFeatures = parserOptions.ecmaFeatures || {}
const sourceType = parserOptions.sourceType || "script"
scopeManager = getEslintScope().analyze(parsedResult.ast, {
return getEslintScope().analyze(parsedResult.ast, {
ignoreEval: true,
nodejsScope: false,
impliedStrict: ecmaFeatures.impliedStrict,
ecmaVersion,
sourceType,
fallback: getFallbackKeys,
})
return scopeManager
}
}

Expand Down Expand Up @@ -349,20 +362,19 @@ function getScope(scopeManager: ScopeManager, currentNode: Node) {
function markVariableAsUsed(
scopeManager: ScopeManager,
currentNode: Node,
parserOptions: any,
program: Node,
name: string,
) {
const hasGlobalReturn =
parserOptions.ecmaFeatures && parserOptions.ecmaFeatures.globalReturn
const specialScope =
hasGlobalReturn || parserOptions.sourceType === "module"
const currentScope = getScope(scopeManager, currentNode)

// Special Node.js scope means we need to start one level deeper
const initialScope =
currentScope.type === "global" && specialScope
? currentScope.childScopes[0]
: currentScope
let initialScope = currentScope
if (
currentScope.type === "global" &&
currentScope.childScopes.length > 0 &&
// top-level scopes refer to a `Program` node
currentScope.childScopes[0].block === program
) {
initialScope = currentScope.childScopes[0]
}

for (let scope: Scope | null = initialScope; scope; scope = scope.upper) {
const variable = scope.variables.find(
Expand Down
58 changes: 49 additions & 9 deletions test/define-custom-blocks-visitor.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ const LINTER_CONFIG = {
"test-no-number-literal": "error",
"test-no-forbidden-key": "error",
"test-no-parsing-error": "error",
"test-no-parsing-error2": "error",
},
}
const noNumberLiteralRule = {
Expand Down Expand Up @@ -61,6 +62,30 @@ const noNoForbiddenKeyRule = {
},
}
const noParsingErrorRule = {
create(context) {
const parseError = context.getSourceCode().parserServices.parseError
if (parseError) {
let loc = undefined
if ("column" in parseError && "lineNumber" in parseError) {
loc = {
line: parseError.lineNumber,
column: parseError.column,
}
}
return {
Program(node) {
context.report({
node,
loc,
message: parseError.message,
})
},
}
}
return {}
},
}
const noParsingErrorRule2 = {
create(context) {
const parseError = context.parserServices.parseError
if (parseError) {
Expand Down Expand Up @@ -131,10 +156,10 @@ function createLinter(target = "json") {
...noParsingErrorRule,
}),
)
linter.defineRule("test-no-parsing-error", (context) =>
linter.defineRule("test-no-parsing-error2", (context) =>
context.parserServices.defineCustomBlocksVisitor(context, jsonParser, {
target,
...noParsingErrorRule,
...noParsingErrorRule2,
}),
)
linter.defineRule("test-no-program-exit", (context) =>
Expand Down Expand Up @@ -297,22 +322,37 @@ describe("parserServices.defineCustomBlocksVisitor tests", () => {

const messages = linter.verify(code, LINTER_CONFIG)

assert.strictEqual(messages.length, 3)
assert.strictEqual(messages.length, 6)
assert.strictEqual(messages[0].message, "Unexpected token ':'.")
assert.strictEqual(messages[0].line, 3)
assert.strictEqual(messages[0].column, 6)
assert.strictEqual(messages[1].message, "Unexpected token ':'.")
assert.strictEqual(messages[1].line, 3)
assert.strictEqual(messages[1].column, 6)
assert.strictEqual(
messages[1].message,
messages[2].message,
"Expected to be an expression, but got empty.",
)
assert.strictEqual(messages[1].line, 5)
assert.strictEqual(messages[1].column, 19)
assert.strictEqual(messages[2].line, 5)
assert.strictEqual(messages[2].column, 19)
assert.strictEqual(
messages[2].message,
messages[3].message,
"Expected to be an expression, but got empty.",
)
assert.strictEqual(messages[2].line, 6)
assert.strictEqual(messages[2].column, 19)
assert.strictEqual(messages[3].line, 5)
assert.strictEqual(messages[3].column, 19)
assert.strictEqual(
messages[4].message,
"Expected to be an expression, but got empty.",
)
assert.strictEqual(messages[4].line, 6)
assert.strictEqual(messages[4].column, 19)
assert.strictEqual(
messages[5].message,
"Expected to be an expression, but got empty.",
)
assert.strictEqual(messages[5].line, 6)
assert.strictEqual(messages[5].column, 19)
})

it("should work even if error.", () => {
Expand Down