From cfd260b419be2a1396d640b14aa3610080b72dec Mon Sep 17 00:00:00 2001 From: Srijan Paul Date: Mon, 31 Jan 2022 18:40:48 +0530 Subject: [PATCH 1/6] added Scope class --- src/visitor/visitor-context.ts | 107 +++++++++++++++++++++++++++++++-- 1 file changed, 102 insertions(+), 5 deletions(-) diff --git a/src/visitor/visitor-context.ts b/src/visitor/visitor-context.ts index 9d3684c..3a7588c 100644 --- a/src/visitor/visitor-context.ts +++ b/src/visitor/visitor-context.ts @@ -1,13 +1,80 @@ import { convertReport, ReportDescriptor } from './ds-utils'; import ASTVisitor from './ast-visitor'; +import { Node, VariableDeclarator } from 'estree'; + +type VarKind = 'let' | 'const' | 'var' | 'global' | 'unknown'; + +/** + * A variable in JS/TS source code emerging either from a var declaration or a parameter + * or the global scope (eg - 'window'). + */ +export type Variable = { + name: string; + kind: VarKind; + // The scope where this variable has been declared. + declScope: Scope; +}; + +class Scope { + private readonly variables = new Map(); + readonly parent: Scope | null; + + constructor(parentScope?: Scope) { + this.parent = parentScope ?? null; + } + + /** + * Checks if a variable is present in this scope. + * Does not check parent scopes. + * @param name Name of the variable to look for + * @returns true if the variable exists. + */ + hasVariable(name: string): boolean { + return this.variables.has(name); + } + + /** + * Find a variable by it's name. Does not check parent scopes. + * @param name Name of the variable to look for + * @returns the variable with name `name` or `null`. + */ + getVariable(name: string): Variable | null { + return this.variables.get(name) ?? null; + } + + /** + * Adds a new variable to the current scope. + * @returns true if a new variable was added, false if an existing variable was overriden. + */ + setVariable(name: string, kind: VarKind): boolean { + const isOverride = this.variables.has(name); + this.variables.set(name, { name, kind, declScope: this}); + return !isOverride; + } + + /** + * Find a variable by it's name. + * Searches the parent scope if it's not found in the current scope. + * @param name Name of the variable to look for. + * @returns The variable called `name`, if it exists. Returns `null` if it doesn't. + */ + searchVariable(name: string): Variable | null { + const varInSelf = this.getVariable(name); + if (varInSelf) return varInSelf; + return this.parent?.searchVariable(name) ?? null; + } +} /** * A CheckerContext encapsulates the current state of an ASTVisitor. */ export default class VisitorContext { - private visitor: ASTVisitor; - private filePath: string; - private sourceString: string; + private readonly visitor: ASTVisitor; + private readonly filePath: string; + private readonly sourceString: string; + + private currentScope = new Scope(); + readonly globalScope = this.currentScope; constructor(visitor: ASTVisitor, filePath: string, sourceString: string) { this.visitor = visitor; @@ -15,8 +82,20 @@ export default class VisitorContext { this.sourceString = sourceString; } - getScope() { - // TODO: + /** + * Find's a variable by it's name. Returns `null` if no such variable is found. + */ + findVarByName(name: string): Variable | null { + return this.currentScope.searchVariable(name); + } + + /** + * Adds a new variable to the current scope. + * @returns `true` if a new variable was added. + * `false` if an old one was overwritten. + */ + addVariableToScope(name: string, kind?: VarKind): boolean { + return this.currentScope.setVariable(name, kind || 'unknown'); } /** @@ -25,6 +104,24 @@ export default class VisitorContext { * @property {SourcePosition|Location} loc */ + // Returns the current scope. + getScope(): Scope { + return this.currentScope; + } + + // Enter a new block scope + enterScope() { + const newScope = new Scope(this.currentScope); + this.currentScope = newScope; + } + + // Exit the currently active scope + exitScope() { + const prevScope = this.currentScope.parent; + if (!prevScope) throw new Error("Attempt to exit global scope"); + this.currentScope = prevScope; + } + // Raise an issue. report(reportDesc: ReportDescriptor): void { const finalReport = convertReport(reportDesc, this.filePath); From 40816cd65639b94be08d0666f48d6cc829ddc2e0 Mon Sep 17 00:00:00 2001 From: Srijan Paul Date: Tue, 1 Feb 2022 11:27:26 +0530 Subject: [PATCH 2/6] added parent embellishment to nodes --- src/check.ts | 2 +- src/util.ts | 8 +- src/visitor/ast-visitor.ts | 133 ++++++++++++++++++++------------- src/visitor/visitor-context.ts | 9 ++- 4 files changed, 97 insertions(+), 55 deletions(-) diff --git a/src/check.ts b/src/check.ts index 4b50e32..9c7f8dc 100644 --- a/src/check.ts +++ b/src/check.ts @@ -1,5 +1,5 @@ import ESTree from 'estree'; -import JsNodeNames from './util'; +import { JsNodeNames } from './util'; import VisitorContext from './visitor/visitor-context'; type NodeVisitorFunc = (ctx: VisitorContext, node: T) => void; diff --git a/src/util.ts b/src/util.ts index aaf2baf..9f4abe4 100644 --- a/src/util.ts +++ b/src/util.ts @@ -68,4 +68,10 @@ export const JsNodeNames = new Set([ 'WithStatement', ]); -export default JsNodeNames; +export function assert(value: T, msg?: string): T { + msg = msg || "assertion failed!"; + if (!value) { + throw new Error(msg); + } + return value; +} diff --git a/src/visitor/ast-visitor.ts b/src/visitor/ast-visitor.ts index 474fcca..1cb93a7 100644 --- a/src/visitor/ast-visitor.ts +++ b/src/visitor/ast-visitor.ts @@ -6,15 +6,18 @@ import { BinaryExpression, BlockStatement, CallExpression, + CatchClause, DoWhileStatement, ExpressionStatement, ForInStatement, ForOfStatement, ForStatement, + Identifier, IfStatement, MemberExpression, Node, ObjectExpression, + Pattern, Program, Property, ReturnStatement, @@ -30,10 +33,18 @@ import Check from '../check'; import VisitorContext from './visitor-context'; import { Issue } from './ds-utils'; +import { assert } from '../util'; + type IChecksForNodeName = { [k: string]: Check[]; }; +export type NodeParentExtension = { + parent?: Node; +}; + +export type WithParent = T & NodeParentExtension; + /** * A base AST visitor class that recursively visits every AST Node and executes the checks */ @@ -100,10 +111,14 @@ export default class ASTVisitor { * Visit an AST Node, executing all corresponding checks and recusrively * visiting it's children . * @param node The node to visit. + * @param parent Parent / surrounding node of `node`. */ - visit(node?: Node | null): void { + visit(node: Node | null, parent?: Node): void { if (!node) return; + // Extend the node with it's parent so that checks can refer to it. + (node as WithParent).parent = parent; + const { type } = node; // 1. Look for all rules that are concerned with this node type // and call them. @@ -117,140 +132,158 @@ export default class ASTVisitor { } // 2. Call the visitor's own function for this node type. - // TODO (injuly): Once all the nodes are covered in our visitor, - // this `if` statement should be replaced with an assertion. // @ts-ignore - if (this[type]) { - // @ts-ignore - this[type](node); - } + if (this[type]) this[type](node); } Program(node: Program): void { + this.context.enterScope(node); for (const stat of node.body) { - this.visit(stat); + this.visit(stat, node); } } VariableDeclaration(node: VariableDeclaration): void { for (const decl of node.declarations) { - this.visit(decl); + this.visit(decl, node); } } - VariableDeclarator(node: VariableDeclarator): void { - this.visit(node.id); - if (node.init) this.visit(node.init); + VariableDeclarator(node: WithParent): void { + const { id, init } = node; + + const declaration = node.parent; + assert( + declaration && declaration.type === 'VariableDeclaration', + 'parent of declarator must be declaration' + ); + if (!declaration) return; + + const { kind } = declaration as VariableDeclaration; + assert(typeof kind === 'string'); + + function addVarsToScope(lhs: Pattern, parent: Node): void { + // TODO + } + + addVarsToScope(id, node); + this.visit(id); + if (init) this.visit(init, node); } ArrayExpression(node: ArrayExpression): void { for (const el of node.elements) { - this.visit(el); + this.visit(el, node); } } BlockStatement(node: BlockStatement): void { for (const stat of node.body) { - this.visit(stat); + this.visit(stat, node); } } ExpressionStatement(node: ExpressionStatement): void { - this.visit(node.expression); + this.visit(node.expression, node); } AssignmentExpression(node: AssignmentExpression): void { - this.visit(node.left); - this.visit(node.right); + this.visit(node.left, node); + this.visit(node.right, node); } MemberExpression(node: MemberExpression): void { - this.visit(node.object); - this.visit(node.property); + this.visit(node.object, node); + this.visit(node.property, node); } ObjectExpression(node: ObjectExpression) { for (const property of node.properties) { - this.visit(property); + this.visit(property, node); } } Property(node: Property): void { - this.visit(node.key); - this.visit(node.value); + this.visit(node.key, node); + this.visit(node.value, node); } CallExpression(node: CallExpression): void { - this.visit(node.callee); + this.visit(node.callee, node); for (const arg of node.arguments) { - this.visit(arg); + this.visit(arg, node); } } BinaryExpression(node: BinaryExpression): void { - this.visit(node.left); - this.visit(node.right); + this.visit(node.left, node); + this.visit(node.right, node); } ReturnStatement(node: ReturnStatement): void { - this.visit(node.argument); + this.visit(node.argument ?? null, node); } TemplateLiteral(node: TemplateLiteral): void { for (const exp of node.expressions) { - this.visit(exp); + this.visit(exp, node); } } UnaryExpression(node: UnaryExpression): void { - this.visit(node.argument); + this.visit(node.argument, node); } ForStatement(node: ForStatement): void { - this.visit(node.init); - this.visit(node.test); - this.visit(node.update); - this.visit(node.body); + this.visit(node.init ?? null, node); + this.visit(node.test ?? null, node); + this.visit(node.update ?? null, node); + this.visit(node.body, node); } ForInStatement(node: ForInStatement): void { - this.visit(node.left); - this.visit(node.right); - this.visit(node.body); + this.visit(node.left, node); + this.visit(node.right, node); + this.visit(node.body, node); } ForOfStatement(node: ForOfStatement): void { - this.visit(node.left); - this.visit(node.right); - this.visit(node.body); + this.visit(node.left, node); + this.visit(node.right, node); + this.visit(node.body, node); } WhileStatement(node: WhileStatement): void { - this.visit(node.body); + this.visit(node.body, node); } IfStatement(node: IfStatement): void { - this.visit(node.test); - this.visit(node.consequent); - this.visit(node.alternate); + this.visit(node.test, node); + this.visit(node.consequent, node); + if (node.alternate) this.visit(node.alternate, node); } DoWhileStatement(node: DoWhileStatement): void { - this.visit(node.body); + this.visit(node.body, node); } TryStatement(node: TryStatement): void { - this.visit(node.block); - this.visit(node.handler); - this.visit(node.finalizer); + this.visit(node.block, node); + if (node.handler) this.visit(node.handler, node); + if (node.finalizer) this.visit(node.finalizer, node); + } + + CatchClause(node: CatchClause): void { + this.visit(node.param, node); + this.visit(node.body, node); } ThrowStatement(node: ThrowStatement): void { - this.visit(node.argument); + this.visit(node.argument, node); } AssignmentPattern(node: AssignmentPattern): void { - this.visit(node.left); - this.visit(node.right); + this.visit(node.left, node); + this.visit(node.right, node); } } diff --git a/src/visitor/visitor-context.ts b/src/visitor/visitor-context.ts index 3a7588c..c98c3fb 100644 --- a/src/visitor/visitor-context.ts +++ b/src/visitor/visitor-context.ts @@ -17,10 +17,12 @@ export type Variable = { class Scope { private readonly variables = new Map(); + public readonly node?: Node; readonly parent: Scope | null; - constructor(parentScope?: Scope) { + constructor(node?: Node, parentScope?: Scope) { this.parent = parentScope ?? null; + this.node = node; } /** @@ -78,6 +80,7 @@ export default class VisitorContext { constructor(visitor: ASTVisitor, filePath: string, sourceString: string) { this.visitor = visitor; + this.globalScope = this.currentScope; this.filePath = filePath; this.sourceString = sourceString; } @@ -110,8 +113,8 @@ export default class VisitorContext { } // Enter a new block scope - enterScope() { - const newScope = new Scope(this.currentScope); + enterScope(node: Node) { + const newScope = new Scope(node, this.currentScope); this.currentScope = newScope; } From e042f579375e82fa89e4fddfa1028f6f9a2493c8 Mon Sep 17 00:00:00 2001 From: Srijan Paul Date: Tue, 1 Feb 2022 18:26:03 +0530 Subject: [PATCH 3/6] added bindings to escope --- example/sample-project/foo.js | 8 +- package.json | 1 + src/analyzer.ts | 2 +- src/checks/index.ts | 1 + src/checks/undeclared-var.ts | 15 ++++ src/visitor/ast-visitor.ts | 110 ++++++++++++++++----------- src/visitor/scope-manager.ts | 24 ++++++ src/visitor/scope.d.ts | 85 +++++++++++++++++++++ src/visitor/visitor-context.ts | 134 +++++++-------------------------- tsconfig.json | 4 +- yarn.lock | 49 +++++++++++- 11 files changed, 268 insertions(+), 165 deletions(-) create mode 100644 src/checks/undeclared-var.ts create mode 100644 src/visitor/scope-manager.ts create mode 100644 src/visitor/scope.d.ts diff --git a/example/sample-project/foo.js b/example/sample-project/foo.js index 51f06d3..2b9f80e 100644 --- a/example/sample-project/foo.js +++ b/example/sample-project/foo.js @@ -1,6 +1,2 @@ -var x = 1 - -module.exports = { foo: 2 }; -console.log(x); -module.exports = { foo: 1 }; -console.log(x); +let foo = 1; +console.log(bar); diff --git a/package.json b/package.json index 896e960..92a1878 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@types/esquery": "^1.0.2", "@types/estree": "^0.0.50", "cli-color": "^2.0.1", + "escope": "^3.6.0", "espree": "^9.3.0", "esquery": "^1.4.0", "glob": "^7.2.0", diff --git a/src/analyzer.ts b/src/analyzer.ts index 4819bed..d2bd743 100644 --- a/src/analyzer.ts +++ b/src/analyzer.ts @@ -14,7 +14,7 @@ export function analyzeJS(filePath: string, code: string, visitor?: ASTVisitor) const ast = parseJS(code); const checks = checkDescriptors.map((desc: CheckDescriptor) => new Check(desc)); visitor = visitor || new ASTVisitor(filePath, code, checks); - visitor.visit(ast); + visitor.checkAST(ast); // TODO (@injuly): Instead of logging them, return the reports array. visitor.logReports(); diff --git a/src/checks/index.ts b/src/checks/index.ts index 5e00ae0..251ff4f 100644 --- a/src/checks/index.ts +++ b/src/checks/index.ts @@ -2,4 +2,5 @@ module.exports = [ require('./array-gaps'), require('./multiple-exports'), require('./useless-template'), + require('./undeclared-var') ]; diff --git a/src/checks/undeclared-var.ts b/src/checks/undeclared-var.ts new file mode 100644 index 0000000..ba578ad --- /dev/null +++ b/src/checks/undeclared-var.ts @@ -0,0 +1,15 @@ +import { CheckDescriptor } from "../check"; + +const check: CheckDescriptor = { + cache: {}, + Identifier(context, node) { + const scope = context.getScope(node); + if (!scope) return; + const resolvedVar = context.getVariableByName(node.name, scope); + if (!resolvedVar) { + console.log(node.name); + } + } +} + +module.exports = check; \ No newline at end of file diff --git a/src/visitor/ast-visitor.ts b/src/visitor/ast-visitor.ts index 1cb93a7..871cf12 100644 --- a/src/visitor/ast-visitor.ts +++ b/src/visitor/ast-visitor.ts @@ -1,6 +1,7 @@ import clc from 'cli-color'; import { ArrayExpression, + ArrowFunctionExpression, AssignmentExpression, AssignmentPattern, BinaryExpression, @@ -12,8 +13,11 @@ import { ForInStatement, ForOfStatement, ForStatement, + FunctionDeclaration, + FunctionExpression, Identifier, IfStatement, + ImportDeclaration, MemberExpression, Node, ObjectExpression, @@ -58,7 +62,7 @@ export default class ASTVisitor { * with the node of type `x`. */ private checksForNodeType: IChecksForNodeName = {}; - private context: VisitorContext; + private context?: VisitorContext; // The list of issues reported so far in DeepSource's format. private issues: Issue[] = []; @@ -73,7 +77,6 @@ export default class ASTVisitor { this.checks.forEach(check => this.addCheck(check)); this.source = source; this.filePath = filePath; - this.context = new VisitorContext(this, filePath, source); } /** @@ -107,13 +110,19 @@ export default class ASTVisitor { log(); } + checkAST(program: Program, conf: Object = {}): Issue[] { + this.context = new VisitorContext(this, this.filePath, this.source, program, {}) + this.visit(program); + return this.issues; + } + /** * Visit an AST Node, executing all corresponding checks and recusrively * visiting it's children . * @param node The node to visit. * @param parent Parent / surrounding node of `node`. */ - visit(node: Node | null, parent?: Node): void { + private visit(node: Node | null, parent?: Node): void { if (!node) return; // Extend the node with it's parent so that checks can refer to it. @@ -136,154 +145,165 @@ export default class ASTVisitor { if (this[type]) this[type](node); } - Program(node: Program): void { - this.context.enterScope(node); + private Program(node: Program): void { for (const stat of node.body) { this.visit(stat, node); } } - VariableDeclaration(node: VariableDeclaration): void { + private VariableDeclaration(node: VariableDeclaration): void { for (const decl of node.declarations) { this.visit(decl, node); } } - VariableDeclarator(node: WithParent): void { + private VariableDeclarator(node: WithParent): void { const { id, init } = node; - - const declaration = node.parent; - assert( - declaration && declaration.type === 'VariableDeclaration', - 'parent of declarator must be declaration' - ); - if (!declaration) return; - - const { kind } = declaration as VariableDeclaration; - assert(typeof kind === 'string'); - - function addVarsToScope(lhs: Pattern, parent: Node): void { - // TODO - } - - addVarsToScope(id, node); this.visit(id); if (init) this.visit(init, node); } - ArrayExpression(node: ArrayExpression): void { + private ArrayExpression(node: ArrayExpression): void { for (const el of node.elements) { this.visit(el, node); } } - BlockStatement(node: BlockStatement): void { + private BlockStatement(node: BlockStatement): void { for (const stat of node.body) { this.visit(stat, node); } } - ExpressionStatement(node: ExpressionStatement): void { + private ExpressionStatement(node: ExpressionStatement): void { this.visit(node.expression, node); } - AssignmentExpression(node: AssignmentExpression): void { + private AssignmentExpression(node: AssignmentExpression): void { this.visit(node.left, node); this.visit(node.right, node); } - MemberExpression(node: MemberExpression): void { + private MemberExpression(node: MemberExpression): void { this.visit(node.object, node); this.visit(node.property, node); } - ObjectExpression(node: ObjectExpression) { + private ObjectExpression(node: ObjectExpression): void { for (const property of node.properties) { this.visit(property, node); } } - Property(node: Property): void { + private Property(node: Property): void { this.visit(node.key, node); this.visit(node.value, node); } - CallExpression(node: CallExpression): void { + private CallExpression(node: CallExpression): void { this.visit(node.callee, node); for (const arg of node.arguments) { this.visit(arg, node); } } - BinaryExpression(node: BinaryExpression): void { + private BinaryExpression(node: BinaryExpression): void { this.visit(node.left, node); this.visit(node.right, node); } - ReturnStatement(node: ReturnStatement): void { + private ReturnStatement(node: ReturnStatement): void { this.visit(node.argument ?? null, node); } - TemplateLiteral(node: TemplateLiteral): void { + private TemplateLiteral(node: TemplateLiteral): void { for (const exp of node.expressions) { this.visit(exp, node); } } - UnaryExpression(node: UnaryExpression): void { + private UnaryExpression(node: UnaryExpression): void { this.visit(node.argument, node); } - ForStatement(node: ForStatement): void { + private ForStatement(node: ForStatement): void { this.visit(node.init ?? null, node); this.visit(node.test ?? null, node); this.visit(node.update ?? null, node); this.visit(node.body, node); } - ForInStatement(node: ForInStatement): void { + private ForInStatement(node: ForInStatement): void { this.visit(node.left, node); this.visit(node.right, node); this.visit(node.body, node); } - ForOfStatement(node: ForOfStatement): void { + private ForOfStatement(node: ForOfStatement): void { this.visit(node.left, node); this.visit(node.right, node); this.visit(node.body, node); } - WhileStatement(node: WhileStatement): void { + private WhileStatement(node: WhileStatement): void { this.visit(node.body, node); } - IfStatement(node: IfStatement): void { + private IfStatement(node: IfStatement): void { this.visit(node.test, node); this.visit(node.consequent, node); if (node.alternate) this.visit(node.alternate, node); } - DoWhileStatement(node: DoWhileStatement): void { + private DoWhileStatement(node: DoWhileStatement): void { this.visit(node.body, node); } - TryStatement(node: TryStatement): void { + private TryStatement(node: TryStatement): void { this.visit(node.block, node); if (node.handler) this.visit(node.handler, node); if (node.finalizer) this.visit(node.finalizer, node); } - CatchClause(node: CatchClause): void { + private CatchClause(node: CatchClause): void { this.visit(node.param, node); this.visit(node.body, node); } - ThrowStatement(node: ThrowStatement): void { + private ThrowStatement(node: ThrowStatement): void { this.visit(node.argument, node); } - AssignmentPattern(node: AssignmentPattern): void { + private AssignmentPattern(node: AssignmentPattern): void { this.visit(node.left, node); this.visit(node.right, node); } + + private ImportDeclaration(node: ImportDeclaration): void { + this.visit(node.source, node); + for (const specifier of node.specifiers) { + this.visit(specifier, node); + } + } + + private FunctionDeclaration(node: FunctionDeclaration): void { + this.visit(node.id, node); + this.visit(node.body, node); + for (const param of node.params) { + this.visit(param, node); + } + } + + FunctionExpression(node: FunctionExpression): void { + if (node.id) this.visit(node.id, node); + this.visit(node.body, node); + for (const param of node.params) this.visit(param, node); + } + + ArrowFunctionExpression(node: ArrowFunctionExpression): void { + this.visit(node.body, node); + for (const param of node.params) { + this.visit(param, node); + } + } } diff --git a/src/visitor/scope-manager.ts b/src/visitor/scope-manager.ts new file mode 100644 index 0000000..fc648c9 --- /dev/null +++ b/src/visitor/scope-manager.ts @@ -0,0 +1,24 @@ +import { Node } from 'estree'; +import { Scope } from './scope'; +const escope = require('escope'); + +export type ESCopeOptions = { + optimistic?: boolean; + directive?: boolean; + // whether to check 'eval()' calls `false` by default. + ignoreEval?: boolean; + // whether the whole script is executed under node.js environment. When enabled, escope adds + // a function scope immediately following the global scope. + nodejsScope?: boolean; + impliedStrict?: boolean; + sourceType?: 'script' | 'module'; + // which ECMAScript version is considered + ecmaVersion?: number; + childVisitorKeys?: Object; + fallback?: 'iteration'; +}; + +export function analyzeScope(ast: Node, options: ESCopeOptions): Scope.ScopeManager { + options.ecmaVersion = 13; + return escope.analyze(ast, options); +} diff --git a/src/visitor/scope.d.ts b/src/visitor/scope.d.ts new file mode 100644 index 0000000..e467da1 --- /dev/null +++ b/src/visitor/scope.d.ts @@ -0,0 +1,85 @@ +import ESTree from 'estree'; +export namespace Scope { + interface ScopeManager { + scopes: Scope[]; + globalScope: Scope | null; + acquire(node: ESTree.Node, inner?: boolean): Scope | null; + getDeclaredVariables(node: ESTree.Node): Variable[]; + } + + interface Scope { + type: + | 'block' + | 'catch' + | 'class' + | 'for' + | 'function' + | 'function-expression-name' + | 'global' + | 'module' + | 'switch' + | 'with' + | 'TDZ'; + isStrict: boolean; + upper: Scope | null; + childScopes: Scope[]; + variableScope: Scope; + block: ESTree.Node; + variables: Variable[]; + set: Map; + references: Reference[]; + through: Reference[]; + functionExpressionScope: boolean; + } + + interface Variable { + name: string; + identifiers: ESTree.Identifier[]; + references: Reference[]; + defs: Definition[]; + } + + interface Reference { + identifier: ESTree.Identifier; + from: Scope; + resolved: Variable | null; + writeExpr: ESTree.Node | null; + init: boolean; + + isWrite(): boolean; + isRead(): boolean; + isWriteOnly(): boolean; + isReadOnly(): boolean; + isReadWrite(): boolean; + } + + type DefinitionType = + | { type: 'CatchClause'; node: ESTree.CatchClause; parent: null } + | { type: 'ClassName'; node: ESTree.ClassDeclaration | ESTree.ClassExpression; parent: null } + | { + type: 'FunctionName'; + node: ESTree.FunctionDeclaration | ESTree.FunctionExpression; + parent: null; + } + | { type: 'ImplicitGlobalVariable'; node: ESTree.Program; parent: null } + | { + type: 'ImportBinding'; + node: + | ESTree.ImportSpecifier + | ESTree.ImportDefaultSpecifier + | ESTree.ImportNamespaceSpecifier; + parent: ESTree.ImportDeclaration; + } + | { + type: 'Parameter'; + node: + | ESTree.FunctionDeclaration + | ESTree.FunctionExpression + | ESTree.ArrowFunctionExpression; + parent: null; + } + | { type: 'TDZ'; node: any; parent: null } + | { type: 'Variable'; node: ESTree.VariableDeclarator; parent: ESTree.VariableDeclaration }; + + type Definition = DefinitionType & { name: ESTree.Identifier }; +} diff --git a/src/visitor/visitor-context.ts b/src/visitor/visitor-context.ts index c98c3fb..3ebd577 100644 --- a/src/visitor/visitor-context.ts +++ b/src/visitor/visitor-context.ts @@ -1,71 +1,8 @@ import { convertReport, ReportDescriptor } from './ds-utils'; import ASTVisitor from './ast-visitor'; -import { Node, VariableDeclarator } from 'estree'; - -type VarKind = 'let' | 'const' | 'var' | 'global' | 'unknown'; - -/** - * A variable in JS/TS source code emerging either from a var declaration or a parameter - * or the global scope (eg - 'window'). - */ -export type Variable = { - name: string; - kind: VarKind; - // The scope where this variable has been declared. - declScope: Scope; -}; - -class Scope { - private readonly variables = new Map(); - public readonly node?: Node; - readonly parent: Scope | null; - - constructor(node?: Node, parentScope?: Scope) { - this.parent = parentScope ?? null; - this.node = node; - } - - /** - * Checks if a variable is present in this scope. - * Does not check parent scopes. - * @param name Name of the variable to look for - * @returns true if the variable exists. - */ - hasVariable(name: string): boolean { - return this.variables.has(name); - } - - /** - * Find a variable by it's name. Does not check parent scopes. - * @param name Name of the variable to look for - * @returns the variable with name `name` or `null`. - */ - getVariable(name: string): Variable | null { - return this.variables.get(name) ?? null; - } - - /** - * Adds a new variable to the current scope. - * @returns true if a new variable was added, false if an existing variable was overriden. - */ - setVariable(name: string, kind: VarKind): boolean { - const isOverride = this.variables.has(name); - this.variables.set(name, { name, kind, declScope: this}); - return !isOverride; - } - - /** - * Find a variable by it's name. - * Searches the parent scope if it's not found in the current scope. - * @param name Name of the variable to look for. - * @returns The variable called `name`, if it exists. Returns `null` if it doesn't. - */ - searchVariable(name: string): Variable | null { - const varInSelf = this.getVariable(name); - if (varInSelf) return varInSelf; - return this.parent?.searchVariable(name) ?? null; - } -} +import { Scope } from './scope'; +import ESTree from 'estree'; +import { analyzeScope, ESCopeOptions } from './scope-manager'; /** * A CheckerContext encapsulates the current state of an ASTVisitor. @@ -74,55 +11,36 @@ export default class VisitorContext { private readonly visitor: ASTVisitor; private readonly filePath: string; private readonly sourceString: string; - - private currentScope = new Scope(); - readonly globalScope = this.currentScope; - - constructor(visitor: ASTVisitor, filePath: string, sourceString: string) { + private scopeManager: Scope.ScopeManager; + private ast: ESTree.Program; + + constructor( + visitor: ASTVisitor, + filePath: string, + sourceString: string, + ast: ESTree.Program, + config: ESCopeOptions + ) { this.visitor = visitor; - this.globalScope = this.currentScope; this.filePath = filePath; this.sourceString = sourceString; + this.ast = ast; + this.scopeManager = analyzeScope(ast, config); } - /** - * Find's a variable by it's name. Returns `null` if no such variable is found. - */ - findVarByName(name: string): Variable | null { - return this.currentScope.searchVariable(name); - } - - /** - * Adds a new variable to the current scope. - * @returns `true` if a new variable was added. - * `false` if an old one was overwritten. - */ - addVariableToScope(name: string, kind?: VarKind): boolean { - return this.currentScope.setVariable(name, kind || 'unknown'); - } - - /** - * @typedef {Object} Report An object describing an issue raised. - * @property {string} message - * @property {SourcePosition|Location} loc - */ - - // Returns the current scope. - getScope(): Scope { - return this.currentScope; - } - - // Enter a new block scope - enterScope(node: Node) { - const newScope = new Scope(node, this.currentScope); - this.currentScope = newScope; + // Get the scope assosciated with `node`. + getScope(node: ESTree.Node): Scope.Scope | null { + return this.scopeManager.acquire(node); } - // Exit the currently active scope - exitScope() { - const prevScope = this.currentScope.parent; - if (!prevScope) throw new Error("Attempt to exit global scope"); - this.currentScope = prevScope; + getVariableByName(name: string, initScope: Scope.Scope): Scope.Variable | null { + let scope: Scope.Scope | null = initScope; + while (scope) { + const variable = scope.set.get(name); + if (variable) return variable; + scope = scope.upper; + } + return null; } // Raise an issue. diff --git a/tsconfig.json b/tsconfig.json index 7138926..4f818d5 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,7 +7,9 @@ "strict": true /* Enable all strict type-checking options. */, "outDir": "build", "moduleResolution": "node", - "allowSyntheticDefaultImports": true + "allowSyntheticDefaultImports": true, + "checkJs": true, + "allowJs": true }, "include": ["src"] } diff --git a/yarn.lock b/yarn.lock index b4dbe78..eacb965 100644 --- a/yarn.lock +++ b/yarn.lock @@ -344,7 +344,7 @@ es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@ es6-symbol "~3.1.3" next-tick "~1.0.0" -es6-iterator@^2.0.3, es6-iterator@~2.0.3: +es6-iterator@^2.0.3, es6-iterator@~2.0.1, es6-iterator@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-iterator/-/es6-iterator-2.0.3.tgz#a7de889141a05a94b0854403b2d0a0fbfa98f3b7" integrity sha1-p96IkUGgWpSwhUQDstCg+/qY87c= @@ -353,7 +353,38 @@ es6-iterator@^2.0.3, es6-iterator@~2.0.3: es5-ext "^0.10.35" es6-symbol "^3.1.1" -es6-symbol@^3.1.1, es6-symbol@~3.1.3: +es6-map@^0.1.3: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-map/-/es6-map-0.1.5.tgz#9136e0503dcc06a301690f0bb14ff4e364e949f0" + integrity sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA= + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-set "~0.1.5" + es6-symbol "~3.1.1" + event-emitter "~0.3.5" + +es6-set@~0.1.5: + version "0.1.5" + resolved "https://registry.yarnpkg.com/es6-set/-/es6-set-0.1.5.tgz#d2b3ec5d4d800ced818db538d28974db0a73ccb1" + integrity sha1-0rPsXU2ADO2BjbU40ol02wpzzLE= + dependencies: + d "1" + es5-ext "~0.10.14" + es6-iterator "~2.0.1" + es6-symbol "3.1.1" + event-emitter "~0.3.5" + +es6-symbol@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" + integrity sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc= + dependencies: + d "1" + es5-ext "~0.10.14" + +es6-symbol@^3.1.1, es6-symbol@~3.1.1, es6-symbol@~3.1.3: version "3.1.3" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== @@ -361,7 +392,7 @@ es6-symbol@^3.1.1, es6-symbol@~3.1.3: d "^1.0.1" ext "^1.1.2" -es6-weak-map@^2.0.3: +es6-weak-map@^2.0.1, es6-weak-map@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/es6-weak-map/-/es6-weak-map-2.0.3.tgz#b6da1f16cc2cc0d9be43e6bdbfc5e7dfcdf31d53" integrity sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA== @@ -376,6 +407,16 @@ escape-string-regexp@^4.0.0: resolved "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== +escope@^3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/escope/-/escope-3.6.0.tgz#e01975e812781a163a6dadfdd80398dc64c889c3" + integrity sha1-4Bl16BJ4GhY6ba392AOY3GTIicM= + dependencies: + es6-map "^0.1.3" + es6-weak-map "^2.0.1" + esrecurse "^4.1.0" + estraverse "^4.1.1" + eslint-scope@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -504,7 +545,7 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== -event-emitter@^0.3.5: +event-emitter@^0.3.5, event-emitter@~0.3.5: version "0.3.5" resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39" integrity sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk= From 696c77bc4c484bfea09d8a37eca1e3235820f9f4 Mon Sep 17 00:00:00 2001 From: Srijan Paul Date: Wed, 2 Feb 2022 16:02:46 +0530 Subject: [PATCH 4/6] context now keeps track of the current scope --- example/sample-project/bar.js | 1 + src/checks/undeclared-var.ts | 4 +- src/util.ts | 72 +++++++++++++++++++++++++++++++++- src/visitor/ast-visitor.ts | 27 ++++++++++++- src/visitor/visitor-context.ts | 10 ++++- 5 files changed, 107 insertions(+), 7 deletions(-) diff --git a/example/sample-project/bar.js b/example/sample-project/bar.js index a29f518..ccc7946 100644 --- a/example/sample-project/bar.js +++ b/example/sample-project/bar.js @@ -4,4 +4,5 @@ let x2 = [,,,2, 3] let template = `${xx} x` let t2 = `12 3`; +t2 = 2; let badTemplate = `bad template`; diff --git a/src/checks/undeclared-var.ts b/src/checks/undeclared-var.ts index ba578ad..e2c1142 100644 --- a/src/checks/undeclared-var.ts +++ b/src/checks/undeclared-var.ts @@ -3,7 +3,7 @@ import { CheckDescriptor } from "../check"; const check: CheckDescriptor = { cache: {}, Identifier(context, node) { - const scope = context.getScope(node); + const scope = context.getScope(); if (!scope) return; const resolvedVar = context.getVariableByName(node.name, scope); if (!resolvedVar) { @@ -12,4 +12,4 @@ const check: CheckDescriptor = { } } -module.exports = check; \ No newline at end of file +module.exports = check; diff --git a/src/util.ts b/src/util.ts index 9f4abe4..3a02dc7 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,3 +1,73 @@ +export const ASTNode = { + ArrayExpression: 'ArrayExpression', + ArrayPattern: 'ArrayPattern', + ArrowFunctionExpression: 'ArrowFunctionExpression', + AssignmentExpression: 'AssignmentExpression', + AssignmentPattern: 'AssignmentPattern', + AwaitExpression: 'AwaitExpression', + BinaryExpression: 'BinaryExpression', + BlockStatement: 'BlockStatement', + BreakStatement: 'BreakStatement', + CallExpression: 'CallExpression', + CatchClause: 'CatchClause', + ChainExpression: 'ChainExpression', + ClassBody: 'ClassBody', + ClassDeclaration: 'ClassDeclaration', + ClassExpression: 'ClassExpression', + ConditionalExpression: 'ConditionalExpression', + ContinueStatement: 'ContinueStatement', + DebuggerStatement: 'DebuggerStatement', + DoWhileStatement: 'DoWhileStatement', + EmptyStatement: 'EmptyStatement', + ExportAllDeclaration: 'ExportAllDeclaration', + ExportDefaultDeclaration: 'ExportDefaultDeclaration', + ExportNamedDeclaration: 'ExportNamedDeclaration', + ExportSpecifier: 'ExportSpecifier', + ExpressionStatement: 'ExpressionStatement', + ForInStatement: 'ForInStatement', + ForOfStatement: 'ForOfStatement', + ForStatement: 'ForStatement', + FunctionDeclaration: 'FunctionDeclaration', + FunctionExpression: 'FunctionExpression', + Identifier: 'Identifier', + IfStatement: 'IfStatement', + ImportDeclaration: 'ImportDeclaration', + ImportDefaultSpecifier: 'ImportDefaultSpecifier', + ImportExpression: 'ImportExpression', + ImportNamespaceSpecifier: 'ImportNamespaceSpecifier', + ImportSpecifier: 'ImportSpecifier', + LabeledStatement: 'LabeledStatement', + Literal: 'Literal', + LogicalExpression: 'LogicalExpression', + MemberExpression: 'MemberExpression', + MetaProperty: 'MetaProperty', + MethodDefinition: 'MethodDefinition', + NewExpression: 'NewExpression', + ObjectExpression: 'ObjectExpression', + ObjectPattern: 'ObjectPattern', + Program: 'Program', + Property: 'Property', + RestElement: 'RestElement', + ReturnStatement: 'ReturnStatement', + SequenceExpression: 'SequenceExpression', + SpreadElement: 'SpreadElement', + Super: 'Super', + SwitchCase: 'SwitchCase', + SwitchStatement: 'SwitchStatement', + TaggedTemplateExpression: 'TaggedTemplateExpression', + TemplateElement: 'TemplateElement', + TemplateLiteral: 'TemplateLiteral', + ThisExpression: 'ThisExpression', + ThrowStatement: 'ThrowStatement', + TryStatement: 'TryStatement', + UnaryExpression: 'UnaryExpression', + UpdateExpression: 'UpdateExpression', + VariableDeclaration: 'VariableDeclaration', + VariableDeclarator: 'VariableDeclarator', + WhileStatement: 'WhileStatement', + WithStatement: 'WithStatement', +}; + export const JsNodeNames = new Set([ 'ArrayExpression', 'ArrayPattern', @@ -69,7 +139,7 @@ export const JsNodeNames = new Set([ ]); export function assert(value: T, msg?: string): T { - msg = msg || "assertion failed!"; + msg = msg || 'assertion failed!'; if (!value) { throw new Error(msg); } diff --git a/src/visitor/ast-visitor.ts b/src/visitor/ast-visitor.ts index 871cf12..5e68f8a 100644 --- a/src/visitor/ast-visitor.ts +++ b/src/visitor/ast-visitor.ts @@ -37,7 +37,7 @@ import Check from '../check'; import VisitorContext from './visitor-context'; import { Issue } from './ds-utils'; -import { assert } from '../util'; +import { assert, ASTNode } from '../util'; type IChecksForNodeName = { [k: string]: Check[]; @@ -57,6 +57,24 @@ export default class ASTVisitor { private source: string; private checks: Check[]; + // Nodes that may affect the scope structure by introducing a new scope + // in the current chain of scopes. + static ScopingNodeTypes = new Set([ + ASTNode.Program, + ASTNode.BlockStatement, + ASTNode.FunctionDeclaration, + ASTNode.FunctionExpression, + ASTNode.ArrowFunctionExpression, + ASTNode.CatchClause, + ASTNode.ForStatement, + ASTNode.ForInStatement, + ASTNode.ForOfStatement, + ASTNode.WhileStatement, + ASTNode.DoWhileStatement, + ASTNode.IfStatement, + ASTNode.WhileStatement, + ]); + /** * `checksForNodeType[x]` Returns a list of all the checks that are concerned * with the node of type `x`. @@ -111,7 +129,7 @@ export default class ASTVisitor { } checkAST(program: Program, conf: Object = {}): Issue[] { - this.context = new VisitorContext(this, this.filePath, this.source, program, {}) + this.context = new VisitorContext(this, this.filePath, this.source, program, {}); this.visit(program); return this.issues; } @@ -140,6 +158,11 @@ export default class ASTVisitor { } } + // If this node affects the scope then make the context reflect that change. + if (ASTVisitor.ScopingNodeTypes.has(type)) { + this.context?.setScopeToNode(node); + } + // 2. Call the visitor's own function for this node type. // @ts-ignore if (this[type]) this[type](node); diff --git a/src/visitor/visitor-context.ts b/src/visitor/visitor-context.ts index 3ebd577..7bb2374 100644 --- a/src/visitor/visitor-context.ts +++ b/src/visitor/visitor-context.ts @@ -11,6 +11,7 @@ export default class VisitorContext { private readonly visitor: ASTVisitor; private readonly filePath: string; private readonly sourceString: string; + private currentScope: Scope.Scope | null = null; private scopeManager: Scope.ScopeManager; private ast: ESTree.Program; @@ -29,8 +30,8 @@ export default class VisitorContext { } // Get the scope assosciated with `node`. - getScope(node: ESTree.Node): Scope.Scope | null { - return this.scopeManager.acquire(node); + getScope(): Scope.Scope | null { + return this.currentScope; } getVariableByName(name: string, initScope: Scope.Scope): Scope.Variable | null { @@ -43,6 +44,11 @@ export default class VisitorContext { return null; } + setScopeToNode(node: ESTree.Node): void { + const scope = this.scopeManager.acquire(node); + if (scope) this.currentScope = scope; + } + // Raise an issue. report(reportDesc: ReportDescriptor): void { const finalReport = convertReport(reportDesc, this.filePath); From 9bd31a2327010726c2dc6b6e039c92112218f3ee Mon Sep 17 00:00:00 2001 From: Srijan Paul Date: Wed, 2 Feb 2022 19:39:33 +0530 Subject: [PATCH 5/6] scopes now popped when exiting a node --- TODO.md | 3 ++- example/sample-project/bar.js | 4 +++- src/check.ts | 3 ++- src/checks/undeclared-var.ts | 15 --------------- src/util.ts | 3 +++ src/visitor/ast-visitor.ts | 12 +++++++++++- src/visitor/visitor-context.ts | 10 ++++++++++ 7 files changed, 31 insertions(+), 19 deletions(-) delete mode 100644 src/checks/undeclared-var.ts diff --git a/TODO.md b/TODO.md index 35a7089..a5f19be 100644 --- a/TODO.md +++ b/TODO.md @@ -1,4 +1,5 @@ - [x] Recursively walk directory and parse the files in it. - [x] Ability to define rules and call them on AST Nodes. - [x] Retain state across visits. -- [ ] Ignore files that match certain patterns. \ No newline at end of file +- [ ] Ignore files that match certain patterns. +- [ ] Add on-exit and on-enter rules. diff --git a/example/sample-project/bar.js b/example/sample-project/bar.js index ccc7946..ec56ceb 100644 --- a/example/sample-project/bar.js +++ b/example/sample-project/bar.js @@ -1,8 +1,10 @@ let x = [1, 2, 3 ,, 4]; let x2 = [,,,2, 3] -let template = `${xx} x` +// let template = `${xx} x` let t2 = `12 3`; t2 = 2; let badTemplate = `bad template`; + +Object.getOwnPropertyNames(globalThis).forEach(x => console.log(x)); diff --git a/src/check.ts b/src/check.ts index 9c7f8dc..b00255d 100644 --- a/src/check.ts +++ b/src/check.ts @@ -1,8 +1,9 @@ import ESTree from 'estree'; import { JsNodeNames } from './util'; +import { NodeParentExtension } from './visitor/ast-visitor'; import VisitorContext from './visitor/visitor-context'; -type NodeVisitorFunc = (ctx: VisitorContext, node: T) => void; +type NodeVisitorFunc = (ctx: VisitorContext, node: T & NodeParentExtension) => void; export type CheckDescriptor = { cache: Record; diff --git a/src/checks/undeclared-var.ts b/src/checks/undeclared-var.ts deleted file mode 100644 index e2c1142..0000000 --- a/src/checks/undeclared-var.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { CheckDescriptor } from "../check"; - -const check: CheckDescriptor = { - cache: {}, - Identifier(context, node) { - const scope = context.getScope(); - if (!scope) return; - const resolvedVar = context.getVariableByName(node.name, scope); - if (!resolvedVar) { - console.log(node.name); - } - } -} - -module.exports = check; diff --git a/src/util.ts b/src/util.ts index 3a02dc7..7879ac1 100644 --- a/src/util.ts +++ b/src/util.ts @@ -145,3 +145,6 @@ export function assert(value: T, msg?: string): T { } return value; } + +// A set of all global variables provided by Node.js runtime. +export const AllGlobalVars = new Set(Object.getOwnPropertyNames(globalThis)); diff --git a/src/visitor/ast-visitor.ts b/src/visitor/ast-visitor.ts index 5e68f8a..e9e0cb8 100644 --- a/src/visitor/ast-visitor.ts +++ b/src/visitor/ast-visitor.ts @@ -84,6 +84,8 @@ export default class ASTVisitor { // The list of issues reported so far in DeepSource's format. private issues: Issue[] = []; + private currentNode: Node | null = null; + /** * @param filePath Path to the JS file (used for issue reporting). * @param source The contents of the JS file. @@ -158,14 +160,22 @@ export default class ASTVisitor { } } + const prevNode = this.currentNode; + this.currentNode = node; // If this node affects the scope then make the context reflect that change. - if (ASTVisitor.ScopingNodeTypes.has(type)) { + const affectsScope = ASTVisitor.ScopingNodeTypes.has(type); + if (affectsScope) { this.context?.setScopeToNode(node); } // 2. Call the visitor's own function for this node type. // @ts-ignore if (this[type]) this[type](node); + + this.currentNode = prevNode; + if (this.currentNode && affectsScope) { + this.context?.setScopeToNode(this.currentNode); + } } private Program(node: Program): void { diff --git a/src/visitor/visitor-context.ts b/src/visitor/visitor-context.ts index 7bb2374..79d47bc 100644 --- a/src/visitor/visitor-context.ts +++ b/src/visitor/visitor-context.ts @@ -34,6 +34,12 @@ export default class VisitorContext { return this.currentScope; } + /** + * Find the object describing a variable by it's name. + * @param name Name of the variable. + * @param initScope The innermost scope where to start looking from. + * @returns + */ getVariableByName(name: string, initScope: Scope.Scope): Scope.Variable | null { let scope: Scope.Scope | null = initScope; while (scope) { @@ -44,6 +50,10 @@ export default class VisitorContext { return null; } + /** + * Set the currently active scope to that of `node`. + * @param node The Node which might have a scope assosciated with it. + */ setScopeToNode(node: ESTree.Node): void { const scope = this.scopeManager.acquire(node); if (scope) this.currentScope = scope; From 7e47915942bd6d7c997676ec00019aab9bfd8f24 Mon Sep 17 00:00:00 2001 From: "deepsource-dev-autofix[bot]" <61578317+deepsource-dev-autofix[bot]@users.noreply.github.com> Date: Wed, 2 Feb 2022 14:12:38 +0000 Subject: [PATCH 6/6] Format code with prettier --- src/check.ts | 5 ++++- src/checks/index.ts | 2 +- src/visitor/visitor-context.ts | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/check.ts b/src/check.ts index b00255d..0ba1d3b 100644 --- a/src/check.ts +++ b/src/check.ts @@ -3,7 +3,10 @@ import { JsNodeNames } from './util'; import { NodeParentExtension } from './visitor/ast-visitor'; import VisitorContext from './visitor/visitor-context'; -type NodeVisitorFunc = (ctx: VisitorContext, node: T & NodeParentExtension) => void; +type NodeVisitorFunc = ( + ctx: VisitorContext, + node: T & NodeParentExtension +) => void; export type CheckDescriptor = { cache: Record; diff --git a/src/checks/index.ts b/src/checks/index.ts index 251ff4f..33f3476 100644 --- a/src/checks/index.ts +++ b/src/checks/index.ts @@ -2,5 +2,5 @@ module.exports = [ require('./array-gaps'), require('./multiple-exports'), require('./useless-template'), - require('./undeclared-var') + require('./undeclared-var'), ]; diff --git a/src/visitor/visitor-context.ts b/src/visitor/visitor-context.ts index 79d47bc..8885837 100644 --- a/src/visitor/visitor-context.ts +++ b/src/visitor/visitor-context.ts @@ -30,7 +30,7 @@ export default class VisitorContext { } // Get the scope assosciated with `node`. - getScope(): Scope.Scope | null { + getScope(): Scope.Scope | null { return this.currentScope; } @@ -38,7 +38,7 @@ export default class VisitorContext { * Find the object describing a variable by it's name. * @param name Name of the variable. * @param initScope The innermost scope where to start looking from. - * @returns + * @returns */ getVariableByName(name: string, initScope: Scope.Scope): Scope.Variable | null { let scope: Scope.Scope | null = initScope;