Navigation Menu

Skip to content

Commit

Permalink
feat(parser): clean up scope-analysis types (#1481)
Browse files Browse the repository at this point in the history
  • Loading branch information
armano2 authored and bradzacher committed Jan 21, 2020
1 parent cde97ac commit 4a727fa
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 62 deletions.
17 changes: 16 additions & 1 deletion packages/experimental-utils/src/ts-eslint-scope/Reference.ts
Expand Up @@ -3,21 +3,36 @@ import ESLintReference from 'eslint-scope/lib/reference';
import { Scope } from './Scope';
import { Variable } from './Variable';

export type ReferenceFlag = 0x1 | 0x2 | 0x3;

interface Reference {
identifier: TSESTree.Identifier;
from: Scope;
resolved: Variable | null;
writeExpr: TSESTree.Node | null;
init: boolean;

partial: boolean;
__maybeImplicitGlobal: boolean;
tainted?: boolean;
typeMode?: boolean;

isWrite(): boolean;
isRead(): boolean;
isWriteOnly(): boolean;
isReadOnly(): boolean;
isReadWrite(): boolean;
}
const Reference = ESLintReference as {
new (): Reference;
new (
identifier: TSESTree.Identifier,
scope: Scope,
flag?: ReferenceFlag,
writeExpr?: TSESTree.Node | null,
maybeImplicitGlobal?: boolean,
partial?: boolean,
init?: boolean,
): Reference;

READ: 0x1;
WRITE: 0x2;
Expand Down
4 changes: 2 additions & 2 deletions packages/experimental-utils/src/ts-eslint-scope/Referencer.ts
Expand Up @@ -44,8 +44,8 @@ interface Referencer<SM extends ScopeManager> extends Visitor {

AssignmentExpression(node: TSESTree.Node): void;
CatchClause(node: TSESTree.Node): void;
Program(node: TSESTree.Node): void;
Identifier(node: TSESTree.Node): void;
Program(node: TSESTree.Program): void;
Identifier(node: TSESTree.Identifier): void;
UpdateExpression(node: TSESTree.Node): void;
MemberExpression(node: TSESTree.Node): void;
Property(node: TSESTree.Node): void;
Expand Down
26 changes: 14 additions & 12 deletions packages/experimental-utils/src/ts-eslint-scope/Scope.ts
Expand Up @@ -15,7 +15,7 @@ import {
ClassScope as ESLintClassScope,
} from 'eslint-scope/lib/scope';
import { Definition } from './Definition';
import { Reference } from './Reference';
import { Reference, ReferenceFlag } from './Reference';
import { ScopeManager } from './ScopeManager';
import { Variable } from './Variable';

Expand Down Expand Up @@ -46,35 +46,37 @@ interface Scope {
references: Reference[];
through: Reference[];
thisFound?: boolean;
taints: Map<string, boolean>;
functionExpressionScope: boolean;
__left: Reference[];

__shouldStaticallyClose(scopeManager: ScopeManager): boolean;
__shouldStaticallyCloseForGlobal(ref: any): boolean;
__staticCloseRef(ref: any): void;
__dynamicCloseRef(ref: any): void;
__globalCloseRef(ref: any): void;
__close(scopeManager: ScopeManager): Scope;
__isValidResolution(ref: any, variable: any): boolean;
__resolve(ref: any): boolean;
__isValidResolution(ref: any, variable: any): variable is Variable;
__resolve(ref: Reference): boolean;
__delegateToUpperScope(ref: any): void;
__addDeclaredVariablesOfNode(variable: any, node: TSESTree.Node): void;
__defineGeneric(
name: any,
set: any,
variables: any,
node: any,
name: string,
set: Map<string, Variable>,
variables: Variable[],
node: TSESTree.Identifier,
def: Definition,
): void;

__define(node: TSESTree.Node, def: Definition): void;

__referencing(
node: TSESTree.Node,
assign: number,
writeExpr: TSESTree.Node,
maybeImplicitGlobal: any,
partial: any,
init: any,
assign?: ReferenceFlag,
writeExpr?: TSESTree.Node,
maybeImplicitGlobal?: any,
partial?: any,
init?: any,
): void;

__detectEval(): void;
Expand Down
Expand Up @@ -16,6 +16,9 @@ interface ScopeManagerOptions {
interface ScopeManager {
__options: ScopeManagerOptions;
__currentScope: Scope;
__nodeToScope: WeakMap<TSESTree.Node, Scope[]>;
__declaredVariables: WeakMap<TSESTree.Node, Variable[]>;

scopes: Scope[];
globalScope: Scope;

Expand All @@ -28,15 +31,15 @@ interface ScopeManager {
isStrictModeSupported(): boolean;

// Returns appropriate scope for this node.
__get(node: TSESTree.Node): Scope;
__get(node: TSESTree.Node): Scope | undefined;
getDeclaredVariables(node: TSESTree.Node): Variable[];
acquire(node: TSESTree.Node, inner?: boolean): Scope | null;
acquireAll(node: TSESTree.Node): Scope | null;
release(node: TSESTree.Node, inner?: boolean): Scope | null;
attach(): void;
detach(): void;

__nestScope(scope: Scope): Scope;
__nestScope<T extends Scope>(scope: T): T;
__nestGlobalScope(node: TSESTree.Node): Scope;
__nestBlockScope(node: TSESTree.Node): Scope;
__nestFunctionScope(node: TSESTree.Node, isMethodDefinition: boolean): Scope;
Expand Down
4 changes: 4 additions & 0 deletions packages/experimental-utils/src/ts-eslint-scope/Variable.ts
Expand Up @@ -2,13 +2,17 @@ import { TSESTree } from '@typescript-eslint/typescript-estree';
import ESLintVariable from 'eslint-scope/lib/variable';
import { Reference } from './Reference';
import { Definition } from './Definition';
import { Scope } from './Scope';

interface Variable {
name: string;
identifiers: TSESTree.Identifier[];
references: Reference[];
defs: Definition[];
eslintUsed?: boolean;
stack?: unknown;
tainted?: boolean;
scope?: Scope;
}

const Variable = ESLintVariable as {
Expand Down
99 changes: 54 additions & 45 deletions packages/parser/tests/tools/scope-analysis.ts
@@ -1,28 +1,33 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import {
TSESTree,
TSESLintScope,
AST_NODE_TYPES,
} from '@typescript-eslint/experimental-utils';
import { ScopeManager } from '../../src/scope/scope-manager';

/** Reference resolver. */
export class ReferenceResolver {
map: Map<any, any>;
export class ReferenceResolver<TKey = unknown> {
map: Map<TKey, { $id: number }>;

constructor() {
this.map = new Map();
}

resolve(obj: any, properties: any): any {
const resolved = Object.assign({ $id: this.map.size }, properties);
resolve<T>(obj: TKey, properties: T): T & { $id: number } {
const resolved = { ...properties, $id: this.map.size };
this.map.set(obj, resolved);
return resolved;
}

ref(obj: any): any {
ref(obj: TKey): { $ref: number } | TKey {
if (typeof obj !== 'object' || obj === null) {
return obj;
}

const map = this.map;
return {
get $ref(): any {
return map.get(obj).$id;
get $ref(): number {
return map.get(obj)!.$id;
},
};
}
Expand All @@ -31,30 +36,38 @@ export class ReferenceResolver {
/**
* Convert a given node object to JSON object.
* This saves only type and range to know what the node is.
* @param {ASTNode} node The AST node object.
* @returns {Object} The object that can be used for JSON.stringify.
* @param node The AST node object.
* @returns The object that can be used for JSON.stringify.
*/
export function nodeToJSON(node: any): any {
export function nodeToJSON(
node: TSESTree.Node | null | undefined,
):
| { type: AST_NODE_TYPES; range: [number, number]; name?: string }
| null
| undefined {
if (!node) {
return node;
}

const { type, name, range } = node;
if (node.type === 'Identifier') {
return { type, name, range };
const { type, range } = node;
if (node.type === AST_NODE_TYPES.Identifier) {
return { type, name: node.name, range };
}
return { type, range };
}

/**
* Convert a given variable object to JSON object.
* @param {Variable} variable The eslint-scope's variable object.
* @param {ReferenceResolver} resolver The reference resolver.
* @returns {Object} The object that can be used for JSON.stringify.
* @param variable The eslint-scope's variable object.
* @param resolver The reference resolver.
* @returns The object that can be used for JSON.stringify.
*/
export function variableToJSON(variable: any, resolver: any): any {
export function variableToJSON(
variable: TSESLintScope.Variable,
resolver: ReferenceResolver,
): unknown {
const { name, eslintUsed } = variable;
const defs = variable.defs.map((d: any) => ({
const defs = variable.defs.map(d => ({
type: d.type,
name: nodeToJSON(d.name),
node: nodeToJSON(d.node),
Expand All @@ -76,11 +89,14 @@ export function variableToJSON(variable: any, resolver: any): any {

/**
* Convert a given reference object to JSON object.
* @param {Reference} reference The eslint-scope's reference object.
* @param {ReferenceResolver} resolver The reference resolver.
* @returns {Object} The object that can be used for JSON.stringify.
* @param reference The eslint-scope's reference object.
* @param resolver The reference resolver.
* @returns The object that can be used for JSON.stringify.
*/
export function referenceToJSON(reference: any, resolver: any): any {
export function referenceToJSON(
reference: TSESLintScope.Reference,
resolver: ReferenceResolver,
): unknown {
const kind = `${reference.isRead() ? 'r' : ''}${
reference.isWrite() ? 'w' : ''
}`;
Expand All @@ -100,35 +116,28 @@ export function referenceToJSON(reference: any, resolver: any): any {

/**
* Convert a given scope object to JSON object.
* @param {Scope} scope The eslint-scope's scope object.
* @param {ReferenceResolver} resolver The reference resolver.
* @param scope The eslint-scope's scope object.
* @param resolver The reference resolver.
* @returns {Object} The object that can be used for JSON.stringify.
*/
export function scopeToJSON(
scope: any,
scope: TSESLintScope.Scope,
resolver = new ReferenceResolver(),
): any {
): unknown {
const { type, functionExpressionScope, isStrict } = scope;
const block = nodeToJSON(scope.block);
const variables = scope.variables.map((v: any) =>
variableToJSON(v, resolver),
);
const references = scope.references.map((r: any) =>
referenceToJSON(r, resolver),
);
const variableMap = Array.from(scope.set.entries()).reduce(
(map: any, [name, variable]: any) => {
map[name] = resolver.ref(variable);
return map;
},
{},
);
const variables = scope.variables.map(v => variableToJSON(v, resolver));
const references = scope.references.map(r => referenceToJSON(r, resolver));
const variableMap = Array.from(scope.set.entries()).reduce<
Record<string, unknown>
>((map, [name, variable]) => {
map[name] = resolver.ref(variable);
return map;
}, {});
const throughReferences = scope.through.map(resolver.ref, resolver);
const variableScope = resolver.ref(scope.variableScope);
const upperScope = resolver.ref(scope.upper);
const childScopes = scope.childScopes.map((c: any) =>
scopeToJSON(c, resolver),
);
const childScopes = scope.childScopes.map(c => scopeToJSON(c, resolver));

return resolver.resolve(scope, {
type,
Expand All @@ -145,12 +154,12 @@ export function scopeToJSON(
});
}

export function getScopeTree(scopeManager: any): any {
export function getScopeTree(scopeManager: ScopeManager): unknown {
const { globalScope } = scopeManager;

// Do the postprocess to test.
// https://github.com/eslint/eslint/blob/84ce72fdeba082b7b132e4ac6b714fb1a93831b7/lib/linter.js#L112-L129
globalScope.through = globalScope.through.filter((reference: any) => {
globalScope.through = globalScope.through.filter(reference => {
const name = reference.identifier.name;
const variable = globalScope.set.get(name);
if (variable) {
Expand Down

0 comments on commit 4a727fa

Please sign in to comment.