Skip to content
This repository has been archived by the owner on Jan 19, 2019. It is now read-only.

Update: add proper scope analysis (fixes #535) #540

Merged
merged 31 commits into from
Nov 13, 2018
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
4e83b52
Update: add proper scope analysis (fixes #535)
mysticatea Nov 4, 2018
1cfbb6e
add computed-properties-in-type fixture
mysticatea Nov 6, 2018
1ec6b3a
add computed-properties-in-interface fixture
mysticatea Nov 6, 2018
80b13cf
add function-overload fixture
mysticatea Nov 6, 2018
3d7cfb2
add method-overload fixture
mysticatea Nov 6, 2018
f61e133
add class-properties fixture
mysticatea Nov 6, 2018
bdd99e9
add decorators fixture
mysticatea Nov 6, 2018
f908e81
update visitor-keys
mysticatea Nov 6, 2018
00ce3dc
add declare-global fixture
mysticatea Nov 7, 2018
bbf0b69
fix typo
mysticatea Nov 7, 2018
257e953
add test for typeof in array destructuring
mysticatea Nov 7, 2018
85b9067
add namespace fixture
mysticatea Nov 7, 2018
7a8e250
add declare-module fixture
mysticatea Nov 7, 2018
8a9eb40
fix crash
mysticatea Nov 7, 2018
5e3b3f6
add declare-function.ts fixture
mysticatea Nov 7, 2018
efeda57
add abstract-class fixture
mysticatea Nov 7, 2018
f5de9b0
add typeof-in-call-signature fixture
mysticatea Nov 8, 2018
7ce5d61
add test for #416
mysticatea Nov 8, 2018
ca4b08f
add test for #435
mysticatea Nov 8, 2018
72f7120
add test for #437
mysticatea Nov 8, 2018
b8b86fd
add test for #443
mysticatea Nov 8, 2018
49ce2da
add test for #459
mysticatea Nov 8, 2018
13f50c3
add test for #466
mysticatea Nov 8, 2018
180cbfe
add test for #471
mysticatea Nov 8, 2018
6e70971
add test for #487
mysticatea Nov 8, 2018
81f4f30
add test for #535
mysticatea Nov 8, 2018
95a8380
add test for #536
mysticatea Nov 8, 2018
14e2499
Merge remote-tracking branch 'origin/master' into fix-scope-analysis
mysticatea Nov 8, 2018
044563b
add test for #476
mysticatea Nov 8, 2018
2a5669d
fix test to use `expect()`
mysticatea Nov 9, 2018
b79045e
Merge remote-tracking branch 'origin/master' into fix-scope-analysis
mysticatea Nov 9, 2018
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
187 changes: 170 additions & 17 deletions analyze-scope.js
Original file line number Diff line number Diff line change
Expand Up @@ -89,14 +89,23 @@ class Referencer extends OriginalReferencer {
id,
new Definition("FunctionName", id, node, null, null, null)
);

// Remove overload definition to avoid confusion of no-redeclare rule.
const { defs, identifiers } = upperScope.set.get(id.name);
for (let i = 0; i < defs.length; ++i) {
const def = defs[i];
if (def.type === "FunctionName" && def.node.type === "TSEmptyBodyFunctionDeclaration") {
defs.splice(i, 1);
identifiers.splice(i, 1);
break;
}
}
} else if (type === "FunctionExpression" && id) {
scopeManager.__nestFunctionExpressionNameScope(node);
}

// Process the type parameters
if (typeParameters) {
this.visit(typeParameters);
}
this.visit(typeParameters);

// Open the function scope.
scopeManager.__nestFunctionScope(node, this.isInnerMethodDefinition);
Expand Down Expand Up @@ -128,17 +137,13 @@ class Referencer extends OriginalReferencer {
}

// Process the return type.
if (returnType) {
this.visit(returnType);
}
this.visit(returnType);

// Process the body.
if (body) {
if (body.type === "BlockStatement") {
this.visitChildren(body);
} else {
this.visit(body);
}
if (body.type === "BlockStatement") {
this.visitChildren(body);
} else {
this.visit(body);
}

// Close the function scope.
Expand All @@ -147,15 +152,37 @@ class Referencer extends OriginalReferencer {

/**
* Override.
* Ignore it in the type mode.
* Visit decorators.
* @param {ClassDeclaration|ClassExpression} node The class node to visit.
* @returns {void}
*/
visitClass(node) {
this.visitDecorators(node.decorators);
super.visitClass(node);
}

/**
* Override.
* Don't create the reference object in the type mode.
* @param {Identifier} node The Identifier node to visit.
* @returns {void}
*/
Identifier(node) {
if (this.typeMode) {
return;
if (!this.typeMode) {
super.Identifier(node);
}
super.Identifier(node);
this.visit(node.typeAnnotation);
}

/**
* Override.
* Visit decorators.
* @param {MethodDefinition} node The MethodDefinition node to visit.
* @returns {void}
*/
MethodDefinition(node) {
this.visitDecorators(node.decorators);
super.MethodDefinition(node);
}

/**
Expand All @@ -177,6 +204,74 @@ class Referencer extends OriginalReferencer {
this.typeMode = false;
}

/**
* Don't create the reference object for the key if not computed.
* @param {TSEmptyBodyFunctionDeclaration} node The TSEmptyBodyFunctionDeclaration node to visit.
* @returns {void}
*/
ClassProperty(node) {
const upperTypeMode = this.typeMode;
const { computed, decorators, key, typeAnnotation, value } = node;

this.typeMode = false;
this.visitDecorators(decorators);
if (computed) {
this.visit(key);
}
this.typeMode = true;
this.visit(typeAnnotation);
this.typeMode = false;
this.visit(value);

this.typeMode = upperTypeMode;
}

/**
* Define the variable of this function declaration only once.
* Because to avoid confusion of `no-redeclare` rule by overloading.
* @param {TSEmptyBodyFunctionDeclaration} node The TSEmptyBodyFunctionDeclaration node to visit.
* @returns {void}
*/
TSEmptyBodyFunctionDeclaration(node) {
const upperTypeMode = this.typeMode;
const scope = this.currentScope();
const { id, typeParameters, params, returnType } = node;

// Ignore this if other overloadings have already existed.
const variable = scope.set.get(id.name);
const defs = variable && variable.defs;
const existed = defs && defs.some(d => d.type === "FunctionName");
if (!existed) {
scope.__define(
id,
new Definition("FunctionName", id, node, null, null, null)
);
}

// Find `typeof` expressions.
this.typeMode = true;
this.visit(typeParameters);
params.forEach(this.visit, this);
this.visit(returnType);
this.typeMode = upperTypeMode;
}

/**
* Create reference objects for the references in parameters and return type.
* @param {TSEmptyBodyFunctionExpression} node The TSEmptyBodyFunctionExpression node to visit.
* @returns {void}
*/
TSEmptyBodyFunctionExpression(node) {
const upperTypeMode = this.typeMode;
const { typeParameters, params, returnType } = node;

this.typeMode = true;
this.visit(typeParameters);
params.forEach(this.visit, this);
this.visit(returnType);
this.typeMode = upperTypeMode;
}

/**
* Don't make variable because it declares only types.
* Switch to the type mode and visit child nodes to find `typeof x` expression in type declarations.
Expand Down Expand Up @@ -224,7 +319,7 @@ class Referencer extends OriginalReferencer {
}

/**
* Create reference objects for the references are in `typeof` expression.
* Create reference objects for the references in `typeof` expression.
* @param {TSTypeQuery} node The TSTypeQuery node to visit.
* @returns {void}
*/
Expand All @@ -238,6 +333,53 @@ class Referencer extends OriginalReferencer {
}
}

/**
* Create reference objects for the references in computed keys.
* @param {TSPropertySignature} node The TSPropertySignature node to visit.
* @returns {void}
*/
TSPropertySignature(node) {
const upperTypeMode = this.typeMode;
const { computed, key, typeAnnotation, initializer } = node;

if (computed) {
this.typeMode = false;
this.visit(key);
this.typeMode = true;
} else {
this.typeMode = true;
this.visit(key);
}
this.visit(typeAnnotation);
this.visit(initializer);

this.typeMode = upperTypeMode;
}

/**
* Create reference objects for the references in computed keys.
* @param {TSMethodSignature} node The TSMethodSignature node to visit.
* @returns {void}
*/
TSMethodSignature(node) {
const upperTypeMode = this.typeMode;
const { computed, key, typeParameters, params, typeAnnotation } = node;

if (computed) {
this.typeMode = false;
this.visit(key);
this.typeMode = true;
} else {
this.typeMode = true;
this.visit(key);
}
this.visit(typeParameters);
params.forEach(this.visit, this);
this.visit(typeAnnotation); // Maybe returnType?

this.typeMode = upperTypeMode;
}

/**
* Create variable object for the enum.
* The enum declaration creates a scope for the enum members.
Expand Down Expand Up @@ -296,6 +438,17 @@ class Referencer extends OriginalReferencer {
this.visit(initializer);
}
}

/**
* Process decorators.
* @param {Decorator[]|undefined} decorators The decorator nodes to visit.
* @returns {void}
*/
visitDecorators(decorators) {
if (decorators) {
decorators.forEach(this.visit, this);
}
}
}

module.exports = function(ast, parserOptions, extraOptions) {
Expand Down
5 changes: 5 additions & 0 deletions tests/fixtures/scope-analysis/class-properties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const s = Symbol()
class A {
a: typeof s
[s]: number
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const s1 = Symbol(), s2 = Symbol()
interface A {
[s1]: number
[s2](s1: number, s2: number): number;
}
5 changes: 5 additions & 0 deletions tests/fixtures/scope-analysis/computed-properties-in-type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const s1 = Symbol(), s2 = Symbol()
type A = {
[s1]: number
[s2](s1: number, s2: number): number;
}
13 changes: 13 additions & 0 deletions tests/fixtures/scope-analysis/decorators.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
function dec(target: any) {
}
function gec() {
return (target: any, proeprtyKey: string) => {}
mysticatea marked this conversation as resolved.
Show resolved Hide resolved
}

@dec
class C {
@gec() field: string
@gec() method(): string {
return ""
}
}
2 changes: 2 additions & 0 deletions tests/fixtures/scope-analysis/function-overload-2.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
function f(): void
function f(a: number): void
5 changes: 5 additions & 0 deletions tests/fixtures/scope-analysis/function-overload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
function f(): void
function f(a: number): void
function f(a?: number): void {
// do something.
}
8 changes: 8 additions & 0 deletions tests/fixtures/scope-analysis/method-overload.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const s = Symbol()
class A {
f(): void
f(a: typeof s): void
f(a?: any): void {
// do something.
}
}
Loading