Skip to content

Commit

Permalink
node, variable polishes
Browse files Browse the repository at this point in the history
  • Loading branch information
guybedford committed Dec 22, 2017
1 parent 50c5355 commit 0183368
Show file tree
Hide file tree
Showing 13 changed files with 87 additions and 50 deletions.
11 changes: 5 additions & 6 deletions src/ast/ExecutionPathOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ import CallOptions from './CallOptions';
import ThisVariable from './variables/ThisVariable';
import ParameterVariable from './variables/ParameterVariable';
import Variable from './variables/Variable';
import VirtualObjectExpression from './nodes/shared/VirtualObjectExpression';
import { UnknownAssignment } from './values';
import { UnknownAssignment, UndefinedAssignment } from './values';

const OPTION_IGNORED_LABELS = 'IGNORED_LABELS';
const OPTION_ACCESSED_NODES = 'ACCESSED_NODES';
Expand All @@ -33,7 +32,7 @@ const RESULT_KEY: RESULT_KEY = {};

/** Wrapper to ensure immutability */
export default class ExecutionPathOptions {
_optionValues: Map<string,any>;
_optionValues: Map<string,Node|Variable>;

/**
* @returns {ExecutionPathOptions}
Expand Down Expand Up @@ -171,7 +170,7 @@ export default class ExecutionPathOptions {
* @return {ParameterVariable[]}
*/
getArgumentsVariables (): ParameterVariable[] {
return this.get(OPTION_ARGUMENTS_VARIABLES) || [];
return <ParameterVariable[]>(this.get(OPTION_ARGUMENTS_VARIABLES) || []);
}

/**
Expand Down Expand Up @@ -225,7 +224,7 @@ export default class ExecutionPathOptions {
* @param {CallOptions} callOptions
* @return {boolean}
*/
hasNodeBeenCalledAtPathWithOptions (path: string[], node: Node, callOptions: CallOptions): boolean {
hasNodeBeenCalledAtPathWithOptions (path: string[], node: Node | UnknownAssignment | UndefinedAssignment, callOptions: CallOptions): boolean {
const previousCallOptions = this._optionValues.getIn([
OPTION_NODES_CALLED_AT_PATH_WITH_OPTIONS,
node,
Expand Down Expand Up @@ -317,7 +316,7 @@ export default class ExecutionPathOptions {
* @param {ParameterVariable[]} variables
* @return {ExecutionPathOptions}
*/
setArgumentsVariables (variables: ParameterVariable[]) {
setArgumentsVariables (variables: (ParameterVariable | Node)[]) {
return this.set(OPTION_ARGUMENTS_VARIABLES, variables);
}

Expand Down
3 changes: 2 additions & 1 deletion src/ast/nodes/CallExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import SpreadElement from './SpreadElement';
import { PredicateFunction } from '../values';
import GlobalVariable from '../variables/GlobalVariable';

// TODO: 3 typing failures because AwaitExpression has no forEachReturnExpressionWhenCalledAtPath

export default class CallExpression extends Node {
type: 'CallExpression';
callee: CallableExpression
Expand All @@ -15,7 +17,6 @@ export default class CallExpression extends Node {

reassignPath (path: string[], options: ExecutionPathOptions) {
!options.hasReturnExpressionBeenAssignedAtPath(path, this) &&
// Type TODO: Failure because AwaitExpression has no forEachReturnExpressionWhenCalledAtPath
this.callee.forEachReturnExpressionWhenCalledAtPath(
[],
this._callOptions,
Expand Down
2 changes: 2 additions & 0 deletions src/ast/nodes/LogicalExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export default class LogicalExpression extends Node {
callback: (options: ExecutionPathOptions) => (node: Node) => void,
options: ExecutionPathOptions
) {
// typing error resolved by ensuring forEachReturnExpressionWhenCalledAtPath
// is on FunctionExpression, ArrowFunctionExpression
this._forEachRelevantBranch((node: Expression) =>
node.forEachReturnExpressionWhenCalledAtPath(
path,
Expand Down
20 changes: 15 additions & 5 deletions src/ast/nodes/MemberExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import CallOptions from '../CallOptions';
import { PredicateFunction } from '../values';
import MagicString from 'magic-string';
import Identifier from './Identifier';
import NamespaceVariable from '../variables/NamespaceVariable';
import ExternalVariable from '../variables/ExternalVariable';

const validProp = /^[a-zA-Z_$][a-zA-Z_$0-9]*$/;

Expand Down Expand Up @@ -42,6 +44,13 @@ class Keypath {
}
}

function isNamespaceVariable (variable: Variable): variable is NamespaceVariable {
return variable.isNamespace;
}
function isExternalVariable (variable: Variable): variable is ExternalVariable {
return variable.isExternal;
}

export default class MemberExpression extends Node {
type: 'MemberExpression';
object: Expression;
Expand All @@ -62,19 +71,19 @@ export default class MemberExpression extends Node {
const keypath = new Keypath(this);

if (!keypath.computed && keypath.root.type === 'Identifier') {
let variable = this.scope.findVariable(keypath.root.name);
let variable: Variable = this.scope.findVariable(keypath.root.name);

while (variable.isNamespace && keypath.parts.length) {
while (isNamespaceVariable(variable) && keypath.parts.length) {
const exporterId = variable.module.id;

const part = keypath.parts[0];
variable = variable.module.traceExport((<Identifier>part).name || (<Literal>part).value);
variable = variable.module.traceExport((<Identifier>part).name || <string>(<Literal>part).value);

if (!variable) {
this.module.warn(
{
code: 'MISSING_EXPORT',
missing: (<Identifier>part).name || (<Literal>part).value,
missing: (<Identifier>part).name || <string>(<Literal>part).value,
importer: relativeId(this.module.id),
exporter: relativeId(exporterId),
message: `'${(<Identifier>part).name || (<Literal>part).value}' is not exported by '${relativeId(exporterId)}'`,
Expand All @@ -96,7 +105,7 @@ export default class MemberExpression extends Node {

this.variable = variable;

if (variable.isExternal) {
if (isExternalVariable(variable)) {
variable.module.suggestName(keypath.root.name);
}
} else {
Expand Down Expand Up @@ -128,6 +137,7 @@ export default class MemberExpression extends Node {
options
);
} else {
// TODO: Type failure for ArrowFunctionExpression, FunctionExpression member object
this.object.forEachReturnExpressionWhenCalledAtPath(
[<string>this._getPathSegment(), ...path],
callOptions,
Expand Down
2 changes: 1 addition & 1 deletion src/ast/nodes/NewExpression.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default class NewExpression extends Node {
);
}

hasEffectsWhenAccessedAtPath (path: string[], options: ExecutionPathOptions) {
hasEffectsWhenAccessedAtPath (path: string[], _options: ExecutionPathOptions) {
return path.length > 1;
}

Expand Down
6 changes: 4 additions & 2 deletions src/ast/nodes/Property.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ export default class Property extends Node {
private _accessorCallOptions: CallOptions;

reassignPath (path: string[], options: ExecutionPathOptions) {
// Typing error caused by forEachReturnExpressionWhenCalledAtPath
// not being available on FunctionExpression, ArrowFunctionExpression
if (this.kind === 'get') {
path.length > 0 &&
(<Expression>this.value).forEachReturnExpressionWhenCalledAtPath(
this.value.forEachReturnExpressionWhenCalledAtPath(
[],
this._accessorCallOptions,
innerOptions => node =>
(innerOptions: ExecutionPathOptions) => (node: Node) =>
node.reassignPath(
path,
innerOptions.addAssignedReturnExpressionAtPath(path, this)
Expand Down
3 changes: 1 addition & 2 deletions src/ast/nodes/shared/disallowIllegalReassignment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@ import Scope from '../../scopes/Scope';
import Node from '../../Node';
import MemberExpression from '../MemberExpression';
import Identifier from '../Identifier';
import NamespaceVariable from '../../variables/NamespaceVariable';

// TODO tidy this up a bit (e.g. they can both use node.module.imports)
export default function disallowIllegalReassignment (scope: Scope, node: Node) {
if (node.type === 'MemberExpression' && (<MemberExpression>node).object.type === 'Identifier') {
const identifier = <Identifier>(<MemberExpression>node).object;
const variable = scope.findVariable(identifier.name);
if ((<NamespaceVariable>variable).isNamespace) {
if (variable.isNamespace) {
node.module.error(
{
code: 'ILLEGAL_NAMESPACE_REASSIGNMENT',
Expand Down
8 changes: 5 additions & 3 deletions src/ast/scopes/FunctionScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export default class FunctionScope extends ReturnValueScope {
variables: {
this: ThisVariable;
default: ExportDefaultVariable;
[name: string]: LocalVariable | GlobalVariable | ExternalVariable
arguments: ArgumentsVariable;
[name: string]: LocalVariable | GlobalVariable | ExternalVariable | ArgumentsVariable;
};

constructor (options = {}) {
Expand All @@ -37,8 +38,9 @@ export default class FunctionScope extends ReturnValueScope {
)
.setArgumentsVariables(
args.map(
(parameter, index) =>
super.getParameterVariables()[index] || parameter
(parameter, index) => {
return super.getParameterVariables()[index] || parameter
}
)
);
}
Expand Down
7 changes: 4 additions & 3 deletions src/ast/scopes/Scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,20 @@ import { UNDEFINED_ASSIGNMENT, UndefinedAssignment, UnknownAssignment } from '..
import ExecutionPathOptions from '../ExecutionPathOptions';
import Identifier from '../nodes/Identifier';
import Expression from '../nodes/Expression';
import Variable from '../variables/Variable';
import ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration';
import Declaration from '../nodes/Declaration';
import GlobalVariable from '../variables/GlobalVariable';
import ExternalVariable from '../variables/ExternalVariable';
import ThisVariable from '../variables/ThisVariable';
import ArgumentsVariable from '../variables/ArgumentsVariable';

export default class Scope {
parent: Scope | void;
variables: {
this: ThisVariable | LocalVariable;
default: ExportDefaultVariable;
[name: string]: LocalVariable | GlobalVariable | ExternalVariable
arguments: ArgumentsVariable;
[name: string]: LocalVariable | GlobalVariable | ExternalVariable | ArgumentsVariable;
};
isModuleScope: boolean;
children: Scope[];
Expand Down Expand Up @@ -106,7 +107,7 @@ export default class Scope {
return (<Scope>this.parent).findLexicalBoundary();
}

findVariable (name: string): ThisVariable | LocalVariable | ExportDefaultVariable | GlobalVariable | ExternalVariable {
findVariable (name: string): ThisVariable | LocalVariable | ExportDefaultVariable | GlobalVariable | ExternalVariable | ArgumentsVariable {
return (
this.variables[name] || (this.parent && this.parent.findVariable(name))
);
Expand Down
65 changes: 43 additions & 22 deletions src/ast/values.ts
Original file line number Diff line number Diff line change
@@ -1,51 +1,72 @@
import Node from './Node';
import ExecutionPathOptions from './ExecutionPathOptions';
import CallOptions from './CallOptions';
import Variable from './variables/Variable';

export const UNKNOWN_VALUE = { toString: () => '[[UNKNOWN]]' };

export type PredicateFunction = (node: Node | UnknownAssignment | UndefinedAssignment) => boolean;
export type PredicateFunction = (node: Variable | Node | UnknownAssignment | UndefinedAssignment) => boolean;

export interface UnknownAssignment {
type: 'UNKNOWN';
reassignPath: () => void;
forEachReturnExpressionWhenCalledAtPath: () => void;
hasEffectsWhenAccessedAtPath: (path: string[], _options: ExecutionPathOptions) => boolean;
hasEffectsWhenAssignedAtPath: (path: string[], _options: ExecutionPathOptions) => boolean;
hasEffectsWhenCalledAtPath: () => true;
someReturnExpressionWhenCalledAtPath: () => true;
reassignPath: (path: string[], options: ExecutionPathOptions) => void;
forEachReturnExpressionWhenCalledAtPath: (
path: string[],
callOptions: CallOptions,
callback: (options: ExecutionPathOptions) => (node: Node) => void,
options: ExecutionPathOptions
) => void;
hasEffectsWhenAccessedAtPath: (path: string[], options: ExecutionPathOptions) => boolean;
hasEffectsWhenAssignedAtPath: (path: string[], options: ExecutionPathOptions) => boolean;
hasEffectsWhenCalledAtPath: (_path: string[], _callOptions: CallOptions, _options: ExecutionPathOptions) => true;
someReturnExpressionWhenCalledAtPath: (
path: string[],
callOptions: CallOptions,
callback: (options: ExecutionPathOptions) => PredicateFunction,
options: ExecutionPathOptions
) => boolean;
toString: () => '[[UNKNOWN]]';
};

export interface UndefinedAssignment {
type: 'UNDEFINED';
reassignPath: () => void;
forEachReturnExpressionWhenCalledAtPath: () => void;
reassignPath: (path: string[], options: ExecutionPathOptions) => void;
forEachReturnExpressionWhenCalledAtPath: (
path: string[],
callOptions: CallOptions,
callback: (options: ExecutionPathOptions) => (node: Node) => void,
options: ExecutionPathOptions
) => void;
hasEffectsWhenAccessedAtPath: (path: string[], _options: ExecutionPathOptions) => boolean;
hasEffectsWhenAssignedAtPath: (path: string[], _options: ExecutionPathOptions) => boolean;
hasEffectsWhenCalledAtPath: (_path: string[], _callOptions: CallOptions, _options: ExecutionPathOptions) => true;
someReturnExpressionWhenCalledAtPath: () => true;
someReturnExpressionWhenCalledAtPath: (
path: string[],
callOptions: CallOptions,
callback: (options: ExecutionPathOptions) => PredicateFunction,
options: ExecutionPathOptions
) => boolean;
toString: () => '[[UNDEFINED]]';
};

export const UNKNOWN_ASSIGNMENT: UnknownAssignment = {
type: 'UNKNOWN',
reassignPath: () => { },
forEachReturnExpressionWhenCalledAtPath: () => { },
hasEffectsWhenAccessedAtPath: (path: string[], _options: ExecutionPathOptions) => path.length > 0,
hasEffectsWhenAssignedAtPath: (path: string[], _options: ExecutionPathOptions) => path.length > 0,
hasEffectsWhenCalledAtPath: () => true,
someReturnExpressionWhenCalledAtPath: () => true,
reassignPath: (_path, _options) => {},
forEachReturnExpressionWhenCalledAtPath: (_path, _callOptions, _callback, _options) => {},
hasEffectsWhenAccessedAtPath: (path, _options) => path.length > 0,
hasEffectsWhenAssignedAtPath: (path, _options) => path.length > 0,
hasEffectsWhenCalledAtPath: (_path, _callOptions, _options) => true,
someReturnExpressionWhenCalledAtPath: (_path, _callOptions, _callback, _options) => true,
toString: () => '[[UNKNOWN]]'
};

export const UNDEFINED_ASSIGNMENT: UndefinedAssignment = {
type: 'UNDEFINED',
reassignPath: () => { },
forEachReturnExpressionWhenCalledAtPath: () => { },
hasEffectsWhenAccessedAtPath: (path: string[], _options: ExecutionPathOptions) => path.length > 0,
hasEffectsWhenAssignedAtPath: (path: string[], _options: ExecutionPathOptions) => path.length > 0,
hasEffectsWhenCalledAtPath: (_path: string[], _callOptions: CallOptions, _options: ExecutionPathOptions) => true,
someReturnExpressionWhenCalledAtPath: () => true,
reassignPath: (_path, _options) => {},
forEachReturnExpressionWhenCalledAtPath: (_path, _callOptions, _callback, _options) => {},
hasEffectsWhenAccessedAtPath: (path, _options) => path.length > 0,
hasEffectsWhenAssignedAtPath: (path, _options) => path.length > 0,
hasEffectsWhenCalledAtPath: (_path, _callOptions, _options) => true,
someReturnExpressionWhenCalledAtPath: (_path, _callOptions, _callback, _options) => true,
toString: () => '[[UNDEFINED]]'
};
2 changes: 1 addition & 1 deletion src/ast/variables/ArgumentsVariable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export default class ArgumentsVariable extends LocalVariable {
callOptions: CallOptions,
predicateFunction: (options: ExecutionPathOptions) => PredicateFunction,
options: ExecutionPathOptions
) {
): boolean {
if (path.length === 0) {
return true;
}
Expand Down
6 changes: 3 additions & 3 deletions src/ast/variables/LocalVariable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { PredicateFunction, UnknownAssignment, UndefinedAssignment } from '../va
import CallOptions from '../CallOptions';
import Identifier from '../nodes/Identifier';
import Node from '../Node';
import Expression from '../nodes/Expression';
import Expression, { CallableExpression } from '../nodes/Expression';
import ExportDefaultDeclaration from '../nodes/ExportDefaultDeclaration';
import Declaration from '../nodes/Declaration';

Expand Down Expand Up @@ -50,7 +50,7 @@ export default class LocalVariable extends Variable {
if (path.length > MAX_PATH_DEPTH) return;
this.boundExpressions.forEachAtPath(
path,
(relativePath: string[], node: Node) =>
(relativePath: string[], node: CallableExpression | UnknownAssignment | UndefinedAssignment) =>
!options.hasNodeBeenCalledAtPathWithOptions(
relativePath,
node,
Expand Down Expand Up @@ -145,7 +145,7 @@ export default class LocalVariable extends Variable {
callOptions: CallOptions,
predicateFunction: (options: ExecutionPathOptions) => PredicateFunction,
options: ExecutionPathOptions
) {
): boolean {
return (
path.length > MAX_PATH_DEPTH ||
(this.included && path.length > 0) ||
Expand Down
2 changes: 1 addition & 1 deletion src/ast/variables/ReplaceableInitializationVariable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default class ReplaceableInitializationVariable extends LocalVariable {
callOptions: CallOptions,
predicateFunction: (options: ExecutionPathOptions) => PredicateFunction,
options: ExecutionPathOptions
) {
): boolean {
return (
this._getInit(options).someReturnExpressionWhenCalledAtPath(
path,
Expand Down

0 comments on commit 0183368

Please sign in to comment.