Rework function scope builders
MODIFY
packages/@romejs/js-compiler/scope/Scope.ts
packages/@romejs/js-compiler/scope/Scope.ts
@@ -11,7 +11,11 @@ import {SCOPE_PRIVATE_PREFIX} from '../constants';
import evaluators from './evaluators/index';
import * as GLOBALS from './globals';
import {Binding} from './bindings';
-import {isValidIdentifierName} from '@romejs/js-ast-utils';
+import {
+ isValidIdentifierName,
+ isVariableIdentifier,
+} from '@romejs/js-ast-utils';
+import Path from '../lib/Path';
let scopeCounter = 0;
@@ -165,8 +169,18 @@ export default class Scope {
return this.bindings.get(name);
}
+ getBindingFromPath(path: Path): undefined | Binding {
+ const {node} = path;
+ if (isVariableIdentifier(node)) {
+ // TODO we can do some isInTypeAnnotation magic to get the proper "type" binding
+ return this.getBinding(node.name);
+ } else {
+ return undefined;
+ }
+ }
+
getBinding(name: string): undefined | Binding {
- const binding = this.getOwnBinding(name);
+ const binding = this.bindings.get(name);
if (binding !== undefined) {
return binding;
}

MODIFY
packages/@romejs/js-compiler/scope/evaluators/ArrowFunctionExpression.ts
packages/@romejs/js-compiler/scope/evaluators/ArrowFunctionExpression.ts
*/
import Scope from '../Scope';
-import {addFunctionBindings} from '../utils';
import {ArrowFunctionExpression, AnyNode} from '@romejs/js-ast';
export default {
creator: true,
build(node: ArrowFunctionExpression, parent: AnyNode, scope: Scope) {
- const newScope = scope.fork('function', node);
- addFunctionBindings(newScope, node, false);
- return newScope;
+ return scope.evaluate(node.head, node, true);
},
};

MODIFY
packages/@romejs/js-compiler/scope/evaluators/BlockStatement.ts
packages/@romejs/js-compiler/scope/evaluators/BlockStatement.ts
import Scope from '../Scope';
import {BlockStatement, AnyNode} from '@romejs/js-ast';
+import {isFunctionNode} from '@romejs/js-ast-utils';
+import {addVarBindings} from '../utils';
export default {
creator: true,
build(node: BlockStatement, parent: AnyNode, scope: Scope) {
+ if (isFunctionNode(parent) && scope.node !== parent) {
+ scope = scope.evaluate(parent.head, parent, true);
+ }
+
const newScope = scope.fork('block', node);
+
+ if (isFunctionNode(parent) && parent.head.hasHoistedVars) {
+ addVarBindings(newScope, parent);
+ }
+
for (const child of node.body) {
newScope.evaluate(child, node);
}
+
return newScope;
},
};

DELETE
packages/@romejs/js-compiler/scope/evaluators/ClassMethod.ts
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-import Scope from '../Scope';
-import {addFunctionBindings} from '../utils';
-import {ClassMethod, AnyNode} from '@romejs/js-ast';
-
-export default {
- creator: true,
- build(node: ClassMethod, parent: AnyNode, scope: Scope) {
- const newScope = scope.fork('function', node);
- addFunctionBindings(newScope, node);
- return newScope;
- },
-};

MODIFY
packages/@romejs/js-compiler/scope/evaluators/FunctionDeclaration.ts
packages/@romejs/js-compiler/scope/evaluators/FunctionDeclaration.ts
import Scope from '../Scope';
import {FunctionBinding} from '@romejs/js-compiler';
import {FunctionDeclaration, AnyNode} from '@romejs/js-ast';
-import {addFunctionBindings} from '../utils';
export default {
creator: false,
@@ -20,9 +19,5 @@ export default {
scope,
}));
}
-
- const newScope = scope.fork('function', node);
- addFunctionBindings(newScope, node);
- return newScope;
},
};

DELETE
packages/@romejs/js-compiler/scope/evaluators/FunctionExpression.ts
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-import Scope from '../Scope';
-import {LetBinding} from '@romejs/js-compiler';
-import {addFunctionBindings} from '../utils';
-import {FunctionExpression, AnyNode} from '@romejs/js-ast';
-
-export default {
- creator: true,
- build(node: FunctionExpression, parent: AnyNode, scope: Scope) {
- const newScope = scope.fork('function', node);
- if (node.id !== undefined) {
- newScope.addBinding(new LetBinding({
- node: node.id,
- name: node.id.name,
- scope,
- }));
- }
- addFunctionBindings(newScope, node);
- return newScope;
- },
-};

ADD
packages/@romejs/js-compiler/scope/evaluators/FunctionHead.ts
+/**
+ * Copyright (c) Facebook, Inc. and its affiliates.
+ *
+ * This source code is licensed under the MIT license found in the
+ * LICENSE file in the root directory of this source tree.
+ */
+
+import Scope from '../Scope';
+import {AnyNode, FunctionHead} from '@romejs/js-ast';
+import {LetBinding, ArgumentsBinding} from '../bindings';
+import {getBindingIdentifiers} from '@romejs/js-ast-utils';
+
+export default {
+ creator: true,
+ build(node: FunctionHead, parent: AnyNode, parentScope: Scope) {
+ // We already evaluated ourselves
+ if (parentScope.node === node) {
+ return parentScope;
+ }
+
+ const scope = parentScope.fork('function', parent);
+
+ if (parent.type === 'FunctionExpression') {
+ const {id} = parent;
+ if (id !== undefined) {
+ scope.addBinding(new LetBinding({
+ node: id,
+ name: id.name,
+ scope,
+ }));
+ }
+ }
+
+ // Add type parameters
+ scope.evaluate(node.typeParameters, node);
+
+ const params = node.rest === undefined
+ ? node.params
+ : [...node.params, node.rest];
+
+ // Add parameters
+ for (const param of params) {
+ for (const id of getBindingIdentifiers(param)) {
+ scope.addBinding(new LetBinding({
+ node: id,
+ name: id.name,
+ scope,
+ kind: 'parameter',
+ }));
+ }
+ }
+
+ // Add `arguments` binding
+ if (parent.type !== 'ArrowFunctionExpression') {
+ scope.addBinding(new ArgumentsBinding({
+ name: 'arguments',
+ node,
+ scope,
+ }));
+ }
+
+ return scope;
+ },
+};

DELETE
packages/@romejs/js-compiler/scope/evaluators/ObjectMethod.ts
-/**
- * Copyright (c) Facebook, Inc. and its affiliates.
- *
- * This source code is licensed under the MIT license found in the
- * LICENSE file in the root directory of this source tree.
- */
-
-import Scope from '../Scope';
-import {addFunctionBindings} from '../utils';
-import {ObjectMethod, AnyNode} from '@romejs/js-ast';
-
-export default {
- creator: true,
- build(node: ObjectMethod, parent: AnyNode, scope: Scope) {
- const newScope = scope.fork('function', node);
- addFunctionBindings(newScope, node);
- return newScope;
- },
-};

MODIFY
packages/@romejs/js-compiler/scope/evaluators/index.ts
packages/@romejs/js-compiler/scope/evaluators/index.ts
@@ -24,9 +24,6 @@ import FlowDeclareFunction from './FlowDeclareFunction';
import FlowDeclareClass from './FlowDeclareClass';
import TSImportEqualsDeclaration from './TSImportEqualsDeclaration';
import ArrowFunctionExpression from './ArrowFunctionExpression';
-import ClassMethod from './ClassMethod';
-import FunctionExpression from './FunctionExpression';
-import ObjectMethod from './ObjectMethod';
import BlockStatement from './BlockStatement';
import ClassExpression from './ClassExpression';
import CatchClause from './CatchClause';
@@ -36,6 +33,7 @@ import ForOfStatement from './ForOfStatement';
import VariableDeclarationStatement from './VariableDeclarationStatement';
import TSInterfaceDeclaration from './TSInterfaceDeclaration';
import TSDeclareFunction from './TSDeclareFunction';
+import FunctionHead from './FunctionHead';
import {AnyNode} from '@romejs/js-ast';
type ScopeEvaluator = {
@@ -47,6 +45,7 @@ type ScopeEvaluator = {
const evaluators: Map<string, ScopeEvaluator> = new Map();
+evaluators.set('FunctionHead', FunctionHead);
evaluators.set('TSDeclareFunction', TSDeclareFunction);
evaluators.set('ClassDeclaration', ClassDeclaration);
evaluators.set('FunctionDeclaration', FunctionDeclaration);
@@ -68,9 +67,6 @@ evaluators.set('FlowDeclareClass', FlowDeclareClass);
evaluators.set('TypeAliasTypeAnnotation', TypeAliasTypeAnnotation);
evaluators.set('TSImportEqualsDeclaration', TSImportEqualsDeclaration);
evaluators.set('ArrowFunctionExpression', ArrowFunctionExpression);
-evaluators.set('ClassMethod', ClassMethod);
-evaluators.set('FunctionExpression', FunctionExpression);
-evaluators.set('ObjectMethod', ObjectMethod);
evaluators.set('BlockStatement', BlockStatement);
evaluators.set('ClassExpression', ClassExpression);
evaluators.set('CatchClause', CatchClause);

MODIFY
packages/@romejs/js-compiler/scope/utils.ts
packages/@romejs/js-compiler/scope/utils.ts
*/
import Scope from './Scope';
-import {
- LetBinding,
- ArgumentsBinding,
- REDUCE_SKIP_SUBTREE,
-} from '@romejs/js-compiler';
-import {getBindingIdentifiers, isFunctionNode} from '@romejs/js-ast-utils';
-import {AnyFunction, Program} from '@romejs/js-ast';
-
-export function addFunctionBindings(
- scope: Scope,
- node: AnyFunction,
- hasArguments: boolean = true,
-) {
- const {head} = node;
-
- // Add type parameters
- scope.evaluate(head.typeParameters);
-
- const params = head.rest === undefined ? head.params : [
- ...head.params,
- head.rest,
- ];
-
- // Add parameters
- for (const param of params) {
- for (const id of getBindingIdentifiers(param)) {
- // TODO maybe add a `param` binding type?
- scope.addBinding(new LetBinding({
- node: id,
- name: id.name,
- scope,
- kind: 'parameter',
- }));
- }
- }
-
- // Add `arguments` binding
- if (hasArguments) {
- scope.addBinding(new ArgumentsBinding({
- name: 'arguments',
- node,
- scope,
- }));
- }
-
- if (head.hasHoistedVars) {
- addVarBindings(scope, node);
- }
-}
+import {REDUCE_SKIP_SUBTREE} from '@romejs/js-compiler';
+import {isFunctionNode} from '@romejs/js-ast-utils';
+import {Program, AnyFunction} from '@romejs/js-ast';
export function addVarBindings(scope: Scope, topNode: AnyFunction | Program) {
const {context} = scope.getRootScope();

MODIFY
packages/@romejs/js-compiler/transforms/lint/undeclaredVariables.ts
packages/@romejs/js-compiler/transforms/lint/undeclaredVariables.ts
@@ -54,6 +54,7 @@ export default {
NODE_VARIABLES.includes(name);
if (!isDefined) {
+ scope.dump();
path.context.addNodeDiagnostic(
node,
descriptions.LINT.UNDECLARED_VARIABLES(name),

MODIFY
packages/@romejs/js-compiler/transforms/lint/unusedVariables.ts
packages/@romejs/js-compiler/transforms/lint/unusedVariables.ts
@@ -34,7 +34,7 @@ const provider = createHook<State, undefined, AnyNode>({
throw new Error('Expected only Identifier to be dispatched');
}
- const binding = path.scope.getBinding(node.name);
+ const binding = path.scope.getBindingFromPath(path);
// Check if this binding belongs to the scope we're tracking
if (binding === undefined || binding.scope !== state.scope) {
@@ -81,6 +81,7 @@ export default {
const {node, scope} = path;
if (scope.node === node) {
+ let hasBindings = false;
const usedBindings: Dict<boolean> = {};
// Get all the non-exported bindings in this file and mark them as unused
@@ -93,9 +94,14 @@ export default {
continue;
}
+ hasBindings = true;
usedBindings[name] = false;
}
+ if (!hasBindings) {
+ return node;
+ }
+
// For functions, consider all parameters except the last to be used
if (node.type === 'FunctionDeclaration' || node.type ===
'FunctionExpression' || node.type === 'ObjectMethod' || node.type ===
@@ -105,7 +111,6 @@ export default {
}
// For functions that have a single throw statement in the body, consider all their arguments
-
// to be used as this is typically an interface definition
const {body: block} = node;
if (block.type === 'BlockStatement' && block.body.length === 1 &&
@@ -136,7 +141,7 @@ export default {
if (node.type === 'JSXReferenceIdentifier' || node.type ===
'ReferenceIdentifier') {
- return path.callHook(provider, undefined);
+ return path.callHook(provider, undefined, node);
}
return node;

MODIFY
packages/@romejs/js-formatter/node/parentheses.ts
packages/@romejs/js-formatter/node/parentheses.ts
@@ -87,7 +87,7 @@ parens.set('FlowNullableTypeAnnotation', (
return parent.type === 'FlowArrayTypeAnnotation';
});
-parens.set('MemberExpression', function UpdateExpression(
+parens.set('MemberExpression', function(
node: MemberExpression,
parent: AnyNode,
): boolean {
@@ -101,7 +101,7 @@ parens.set('MemberExpression', function UpdateExpression(
}
});
-parens.set('UpdateExpression', function UpdateExpression(
+parens.set('UpdateExpression', function(
node: UpdateExpression,
parent: AnyNode,
): boolean {
@@ -113,7 +113,7 @@ parens.set('UpdateExpression', function UpdateExpression(
);
});
-parens.set('ObjectExpression', function ObjectExpression(
+parens.set('ObjectExpression', function(
node: ObjectExpression,
parent: AnyNode,
printStack: Array<AnyNode>,
@@ -121,7 +121,7 @@ parens.set('ObjectExpression', function ObjectExpression(
return isFirstInStatement(printStack, {considerArrow: true});
});
-parens.set('DoExpression', function DoExpression(
+parens.set('DoExpression', function(
node: DoExpression,
parent: AnyNode,
printStack: Array<AnyNode>,
@@ -129,7 +129,7 @@ parens.set('DoExpression', function DoExpression(
return isFirstInStatement(printStack);
});
-function LogicalExpression(
+function needsParenLogicalExpression(
node: BinaryExpression | LogicalExpression,
parent: AnyNode,
): boolean {
@@ -184,9 +184,9 @@ function LogicalExpression(
}
}
-parens.set('LogicalExpression', LogicalExpression);
+parens.set('LogicalExpression', needsParenLogicalExpression);
-parens.set('BinaryExpression', function BinaryExpression(
+parens.set('BinaryExpression', function(
node: BinaryExpression,
parent: AnyNode,
): boolean {
@@ -194,19 +194,16 @@ parens.set('BinaryExpression', function BinaryExpression(
// for ((1 in []);;);
return node.operator === 'in' && (parent.type === 'VariableDeclarator' ||
- isFor(parent)) || LogicalExpression(node, parent);
+ isFor(parent)) || needsParenLogicalExpression(node, parent);
});
-parens.set('SequenceExpression', function SequenceExpression(
+parens.set('SequenceExpression', function(
node: SequenceExpression,
parent: AnyNode,
): boolean {
if ( // Although parentheses wouldn"t hurt around sequence
-
// expressions in the head of for loops, traditional style
-
// dictates that e.g. i++, j++ should not be wrapped with
-
// parentheses.
parent.type === 'ForStatement' || parent.type === 'ThrowStatement' ||
parent.type ===
@@ -227,7 +224,10 @@ parens.set('SequenceExpression', function SequenceExpression(
return true;
});
-function YieldExpression(node: YieldExpression, parent: AnyNode): boolean {
+function needsParenYieldExpression(
+ node: YieldExpression,
+ parent: AnyNode,
+): boolean {
return isBinary(parent) || isUnaryLike(parent) || parent.type ===
'MemberExpression' || parent.type === 'CallExpression' &&
parent.callee ===
@@ -239,10 +239,10 @@ function YieldExpression(node: YieldExpression, parent: AnyNode): boolean {
node === parent.test || isClassExtendsClause(node, parent);
}
-parens.set('YieldExpression', YieldExpression);
-parens.set('AwaitExpression', YieldExpression);
+parens.set('YieldExpression', needsParenYieldExpression);
+parens.set('AwaitExpression', needsParenYieldExpression);
-parens.set('OptionalCallExpression', function OptionalCallExpression(
+parens.set('OptionalCallExpression', function(
node: OptionalCallExpression,
parent: AnyNode,
): boolean {
@@ -252,7 +252,7 @@ parens.set('OptionalCallExpression', function OptionalCallExpression(
parent.object === node;
});
-parens.set('ClassExpression', function ClassExpression(
+parens.set('ClassExpression', function(
node: ClassExpression,
parent: AnyNode,
printStack: Array<AnyNode>,
@@ -260,7 +260,7 @@ parens.set('ClassExpression', function ClassExpression(
return isFirstInStatement(printStack, {considerDefaultExports: true});
});
-function UnaryExpression(
+function needsParenUnaryExpression(
node:
| UnaryExpression
| ArrowFunctionExpression
@@ -281,11 +281,11 @@ function UnaryExpression(
node || isClassExtendsClause(node, parent);
}
-parens.set('UnaryExpression', UnaryExpression);
-parens.set('SpreadElement', UnaryExpression);
-parens.set('SpreadProperty', UnaryExpression);
+parens.set('UnaryExpression', needsParenUnaryExpression);
+parens.set('SpreadElement', needsParenUnaryExpression);
+parens.set('SpreadProperty', needsParenUnaryExpression);
-parens.set('FunctionExpression', function FunctionExpression(
+parens.set('FunctionExpression', function(
node: AnyNode,
parent: AnyNode,
printStack: Array<AnyNode>,
@@ -293,17 +293,15 @@ parens.set('FunctionExpression', function FunctionExpression(
return isFirstInStatement(printStack, {considerDefaultExports: true});
});
-parens.set('ArrowFunctionExpression', function ArrowFunctionExpression(
+parens.set('ArrowFunctionExpression', function(
node: ArrowFunctionExpression,
parent: AnyNode,
): boolean {
- return parent.type === 'ExportLocalDeclaration' || ConditionalExpression(
- node,
- parent,
- );
+ return parent.type === 'ExportLocalDeclaration' ||
+ needsParenConditionalExpression(node, parent);
});
-function ConditionalExpression(
+function needsParenConditionalExpression(
node: ArrowFunctionExpression | AssignmentExpression | ConditionalExpression,
parent: AnyNode,
): boolean {
@@ -319,23 +317,26 @@ function ConditionalExpression(
return true;
}
- return UnaryExpression(node, parent);
+ return needsParenUnaryExpression(node, parent);
}
-parens.set('ConditionalExpression', ConditionalExpression);
+parens.set('ConditionalExpression', needsParenConditionalExpression);
-parens.set('AssignmentExpression', function AssignmentExpression(
+parens.set('AssignmentExpression', function(
node: AssignmentExpression,
parent: AnyNode,
): boolean {
if (node.left.type === 'AssignmentObjectPattern') {
return true;
} else {
- return ConditionalExpression(node, parent);
+ return needsParenConditionalExpression(node, parent);
}
});
-function UnionTypeAnnotation(node: UnionTypeAnnotation, parent: AnyNode) {
+function needsParenUnionTypeAnnotation(
+ node: UnionTypeAnnotation,
+ parent: AnyNode,
+) {
return parent.type === 'FlowArrayTypeAnnotation' || parent.type ===
'FlowNullableTypeAnnotation' || parent.type ===
'IntersectionTypeAnnotation' ||
@@ -344,17 +345,17 @@ function UnionTypeAnnotation(node: UnionTypeAnnotation, parent: AnyNode) {
'TSOptionalType';
}
-parens.set('UnionTypeAnnotation', UnionTypeAnnotation);
-parens.set('IntersectionTypeAnnotation', UnionTypeAnnotation);
+parens.set('UnionTypeAnnotation', needsParenUnionTypeAnnotation);
+parens.set('IntersectionTypeAnnotation', needsParenUnionTypeAnnotation);
-parens.set('TSInferType', function TSInferType(
- node: TSInferType,
- parent: AnyNode,
-): boolean {
+parens.set(
+ 'TSInferType',
+ function(node: TSInferType, parent: AnyNode): boolean {
return parent.type === 'TSArrayType' || parent.type === 'TSOptionalType';
-});
+ },
+);
-parens.set('FlowFunctionTypeAnnotation', function FlowFunctionTypeAnnotation(
+parens.set('FlowFunctionTypeAnnotation', function(
node: FlowFunctionTypeAnnotation,
parent: AnyNode,
printStack: Array<AnyNode>,

Rework function scope builders