mirror of
https://github.com/bluetech/ng-annotate-patched.git
synced 2026-07-02 00:17:42 +08:00
reference-following support via adapted scope.js and scopetools.js from defs.js project
This commit is contained in:
+1
-1
@@ -3,7 +3,7 @@ echo "beginning ng-annotate defs-build"
|
||||
rm -rf es5
|
||||
mkdir es5
|
||||
|
||||
declare -a files=(generate-sourcemap.js lut.js ng-annotate.js ng-annotate-main.js nginject-comments.js run-tests.js)
|
||||
declare -a files=(generate-sourcemap.js lut.js ng-annotate.js ng-annotate-main.js nginject-comments.js run-tests.js scope.js scopetools.js)
|
||||
DEFS="../node_modules/.bin/defs"
|
||||
if [[ ! -f "$DEFS" ]]; then DEFS="../../../../node_modules/.bin/defs" ; fi
|
||||
if [[ ! -f "$DEFS" ]]; then DEFS="defs" ; fi
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// lut.js
|
||||
// MIT licensed, see LICENSE file
|
||||
// Copyright (c) 2013-2014 Olov Lassus <olov.lassus@gmail.com>
|
||||
|
||||
"use strict";
|
||||
|
||||
const assert = require("assert");
|
||||
|
||||
@@ -11,9 +11,11 @@ const is = require("simple-is");
|
||||
const alter = require("alter");
|
||||
const traverse = require("ordered-ast-traverse");
|
||||
const EOL = require("os").EOL;
|
||||
const assert = require("assert");
|
||||
const ngInjectComments = require("./nginject-comments");
|
||||
const generateSourcemap = require("./generate-sourcemap");
|
||||
const Lut = require("./lut");
|
||||
const scopeTools = require("./scopetools");
|
||||
|
||||
const chainedRouteProvider = 1;
|
||||
const chainedUrlRouterProvider = 2;
|
||||
@@ -354,6 +356,14 @@ function judgeSuspects(ctx) {
|
||||
}
|
||||
|
||||
target = jumpOverIife(target);
|
||||
const followedTarget = followReference(target);
|
||||
if (followedTarget) {
|
||||
if (followedTarget.$once) {
|
||||
continue;
|
||||
}
|
||||
followedTarget.$once = true;
|
||||
target = followedTarget;
|
||||
}
|
||||
|
||||
if (mode === "rebuild" && isAnnotatedArray(target)) {
|
||||
replaceArray(target, fragments, quot);
|
||||
@@ -368,6 +378,33 @@ function judgeSuspects(ctx) {
|
||||
}
|
||||
}
|
||||
|
||||
function followReference(node) {
|
||||
if (!scopeTools.isReference(node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const scope = node.$scope.lookup(node.name);
|
||||
if (!scope) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parent = scope.getNode(node.name).$parent;
|
||||
const kind = scope.getKind(node.name);
|
||||
const ptype = parent.type;
|
||||
|
||||
if (is.someof(kind, ["const", "let", "var"])) {
|
||||
assert(ptype === "VariableDeclarator");
|
||||
return parent.init;
|
||||
} else if (kind === "fun") {
|
||||
assert(ptype === "FunctionDeclaration" || ptype === "FunctionExpression")
|
||||
return parent;
|
||||
}
|
||||
|
||||
// other kinds should not be handled ("param", "caught")
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function judgeInjectArraySuspect(node, ctx) {
|
||||
// /*@ngInject*/ var foo = function($scope) {} and
|
||||
// /*@ngInject*/ function foo($scope) {} and
|
||||
@@ -523,6 +560,8 @@ module.exports = function ngAnnotate(src, options) {
|
||||
|
||||
const lut = new Lut(ast, src);
|
||||
|
||||
scopeTools.setupScopeAndReferences(ast);
|
||||
|
||||
const ctx = {
|
||||
mode: mode,
|
||||
quot: quot,
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
// nginject-comments.js
|
||||
// MIT licensed, see LICENSE file
|
||||
// Copyright (c) 2013-2014 Olov Lassus <olov.lassus@gmail.com>
|
||||
|
||||
"use strict";
|
||||
|
||||
const is = require("simple-is");
|
||||
|
||||
@@ -0,0 +1,158 @@
|
||||
// scope.js
|
||||
// MIT licensed, see LICENSE file
|
||||
// Copyright (c) 2013-2014 Olov Lassus <olov.lassus@gmail.com>
|
||||
|
||||
"use strict";
|
||||
|
||||
const assert = require("assert");
|
||||
const stringmap = require("stringmap");
|
||||
const stringset = require("stringset");
|
||||
const is = require("simple-is");
|
||||
const fmt = require("simple-fmt");
|
||||
|
||||
function Scope(args) {
|
||||
assert(is.someof(args.kind, ["hoist", "block", "catch-block"]));
|
||||
assert(is.object(args.node));
|
||||
assert(args.parent === null || is.object(args.parent));
|
||||
|
||||
// kind === "hoist": function scopes, program scope, injected globals
|
||||
// kind === "block": ES6 block scopes
|
||||
// kind === "catch-block": catch block scopes
|
||||
this.kind = args.kind;
|
||||
|
||||
// the AST node the block corresponds to
|
||||
this.node = args.node;
|
||||
|
||||
// parent scope
|
||||
this.parent = args.parent;
|
||||
|
||||
// children scopes for easier traversal (populated internally)
|
||||
this.children = [];
|
||||
|
||||
// scope declarations. decls[variable_name] = {
|
||||
// kind: "fun" for functions,
|
||||
// "param" for function parameters,
|
||||
// "caught" for catch parameter
|
||||
// "var",
|
||||
// "const",
|
||||
// "let"
|
||||
// node: the AST node the declaration corresponds to
|
||||
// from: source code index from which it is visible at earliest
|
||||
// (only stored for "const", "let" [and "var"] nodes)
|
||||
// }
|
||||
this.decls = stringmap();
|
||||
|
||||
// names of all variables declared outside this hoist scope but
|
||||
// referenced in this scope (immediately or in child).
|
||||
// only stored on hoist scopes for efficiency
|
||||
// (because we currently generate lots of empty block scopes)
|
||||
this.propagates = (this.kind === "hoist" ? stringset() : null);
|
||||
|
||||
// scopes register themselves with their parents for easier traversal
|
||||
if (this.parent) {
|
||||
this.parent.children.push(this);
|
||||
}
|
||||
}
|
||||
|
||||
Scope.prototype.print = function(indent) {
|
||||
indent = indent || 0;
|
||||
const scope = this;
|
||||
const names = this.decls.keys().map(function(name) {
|
||||
return fmt("{0} [{1}]", name, scope.decls.get(name).kind);
|
||||
}).join(", ");
|
||||
const propagates = this.propagates ? this.propagates.items().join(", ") : "";
|
||||
console.log(fmt("{0}{1}: {2}. propagates: {3}", fmt.repeat(" ", indent), this.node.type, names, propagates));
|
||||
this.children.forEach(function(c) {
|
||||
c.print(indent + 2);
|
||||
});
|
||||
};
|
||||
|
||||
Scope.prototype.add = function(name, kind, node, referableFromPos) {
|
||||
assert(is.someof(kind, ["fun", "param", "var", "caught", "const", "let"]));
|
||||
|
||||
function isConstLet(kind) {
|
||||
return is.someof(kind, ["const", "let"]);
|
||||
}
|
||||
|
||||
let scope = this;
|
||||
|
||||
// search nearest hoist-scope for fun, param and var's
|
||||
// const, let and caught variables go directly in the scope (which may be hoist, block or catch-block)
|
||||
if (is.someof(kind, ["fun", "param", "var"])) {
|
||||
while (scope.kind !== "hoist") {
|
||||
// if (scope.decls.has(name) && isConstLet(scope.decls.get(name).kind)) { // could be caught
|
||||
// return error(getline(node), "{0} is already declared", name);
|
||||
// }
|
||||
scope = scope.parent;
|
||||
}
|
||||
}
|
||||
// name exists in scope and either new or existing kind is const|let => error
|
||||
// if (scope.decls.has(name) && (isConstLet(scope.decls.get(name).kind) || isConstLet(kind))) {
|
||||
// return error(getline(node), "{0} is already declared", name);
|
||||
// }
|
||||
|
||||
const declaration = {
|
||||
kind: kind,
|
||||
node: node,
|
||||
};
|
||||
if (referableFromPos) {
|
||||
assert(is.someof(kind, ["var", "const", "let"]));
|
||||
declaration.from = referableFromPos;
|
||||
}
|
||||
scope.decls.set(name, declaration);
|
||||
};
|
||||
|
||||
Scope.prototype.getKind = function(name) {
|
||||
assert(is.string(name));
|
||||
const decl = this.decls.get(name);
|
||||
return decl ? decl.kind : null;
|
||||
};
|
||||
|
||||
Scope.prototype.getNode = function(name) {
|
||||
assert(is.string(name));
|
||||
const decl = this.decls.get(name);
|
||||
return decl ? decl.node : null;
|
||||
};
|
||||
|
||||
Scope.prototype.getFromPos = function(name) {
|
||||
assert(is.string(name));
|
||||
const decl = this.decls.get(name);
|
||||
return decl ? decl.from : null;
|
||||
};
|
||||
|
||||
Scope.prototype.hasOwn = function(name) {
|
||||
return this.decls.has(name);
|
||||
};
|
||||
|
||||
Scope.prototype.remove = function(name) {
|
||||
return this.decls.remove(name);
|
||||
};
|
||||
|
||||
Scope.prototype.doesPropagate = function(name) {
|
||||
return this.propagates.has(name);
|
||||
};
|
||||
|
||||
Scope.prototype.markPropagates = function(name) {
|
||||
this.propagates.add(name);
|
||||
};
|
||||
|
||||
Scope.prototype.closestHoistScope = function() {
|
||||
let scope = this;
|
||||
while (scope.kind !== "hoist") {
|
||||
scope = scope.parent;
|
||||
}
|
||||
return scope;
|
||||
};
|
||||
|
||||
Scope.prototype.lookup = function(name) {
|
||||
for (let scope = this; scope; scope = scope.parent) {
|
||||
if (scope.decls.has(name)) {
|
||||
return scope;
|
||||
} else if (scope.kind === "hoist") {
|
||||
scope.propagates.add(name);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
module.exports = Scope;
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
// scopetools.js
|
||||
// MIT licensed, see LICENSE file
|
||||
// Copyright (c) 2013-2014 Olov Lassus <olov.lassus@gmail.com>
|
||||
|
||||
"use strict";
|
||||
|
||||
const assert = require("assert");
|
||||
const traverse = require("ordered-ast-traverse");
|
||||
const Scope = require("./scope");
|
||||
const is = require("simple-is");
|
||||
|
||||
module.exports = {
|
||||
setupScopeAndReferences: setupScopeAndReferences,
|
||||
isReference: isReference,
|
||||
};
|
||||
|
||||
function setupScopeAndReferences(root) {
|
||||
traverse(root, {pre: createScopes});
|
||||
createTopScope(root.$scope);
|
||||
}
|
||||
|
||||
function createScopes(node, parent) {
|
||||
node.$parent = parent;
|
||||
node.$scope = parent ? parent.$scope : null; // may be overridden
|
||||
|
||||
if (isNonFunctionBlock(node, parent)) {
|
||||
// A block node is a scope unless parent is a function
|
||||
node.$scope = new Scope({
|
||||
kind: "block",
|
||||
node: node,
|
||||
parent: parent.$scope,
|
||||
});
|
||||
|
||||
} else if (node.type === "VariableDeclaration") {
|
||||
// Variable declarations names goes in current scope
|
||||
node.declarations.forEach(function(declarator) {
|
||||
const name = declarator.id.name;
|
||||
node.$scope.add(name, node.kind, declarator.id, declarator.range[1]);
|
||||
});
|
||||
|
||||
} else if (isFunction(node)) {
|
||||
// Function is a scope, with params in it
|
||||
// There's no block-scope under it
|
||||
|
||||
node.$scope = new Scope({
|
||||
kind: "hoist",
|
||||
node: node,
|
||||
parent: parent.$scope,
|
||||
});
|
||||
|
||||
// function has a name
|
||||
if (node.id) {
|
||||
if (node.type === "FunctionDeclaration") {
|
||||
// Function name goes in parent scope for declared functions
|
||||
parent.$scope.add(node.id.name, "fun", node.id, null);
|
||||
} else if (node.type === "FunctionExpression") {
|
||||
// Function name goes in function's scope for named function expressions
|
||||
node.$scope.add(node.id.name, "fun", node.id, null);
|
||||
} else {
|
||||
assert(false);
|
||||
}
|
||||
}
|
||||
|
||||
node.params.forEach(function(param) {
|
||||
node.$scope.add(param.name, "param", param, null);
|
||||
});
|
||||
|
||||
} else if (isForWithConstLet(node) || isForInOfWithConstLet(node)) {
|
||||
// For(In/Of) loop with const|let declaration is a scope, with declaration in it
|
||||
// There may be a block-scope under it
|
||||
node.$scope = new Scope({
|
||||
kind: "block",
|
||||
node: node,
|
||||
parent: parent.$scope,
|
||||
});
|
||||
|
||||
} else if (node.type === "CatchClause") {
|
||||
const identifier = node.param;
|
||||
|
||||
node.$scope = new Scope({
|
||||
kind: "catch-block",
|
||||
node: node,
|
||||
parent: parent.$scope,
|
||||
});
|
||||
node.$scope.add(identifier.name, "caught", identifier, null);
|
||||
|
||||
// All hoist-scope keeps track of which variables that are propagated through,
|
||||
// i.e. an reference inside the scope points to a declaration outside the scope.
|
||||
// This is used to mark "taint" the name since adding a new variable in the scope,
|
||||
// with a propagated name, would change the meaning of the existing references.
|
||||
//
|
||||
// catch(e) is special because even though e is a variable in its own scope,
|
||||
// we want to make sure that catch(e){let e} is never transformed to
|
||||
// catch(e){var e} (but rather var e$0). For that reason we taint the use of e
|
||||
// in the closest hoist-scope, i.e. where var e$0 belongs.
|
||||
node.$scope.closestHoistScope().markPropagates(identifier.name);
|
||||
|
||||
} else if (node.type === "Program") {
|
||||
// Top-level program is a scope
|
||||
// There's no block-scope under it
|
||||
node.$scope = new Scope({
|
||||
kind: "hoist",
|
||||
node: node,
|
||||
parent: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function createTopScope(programScope) {
|
||||
function inject(obj) {
|
||||
for (let name in obj) {
|
||||
const writeable = obj[name];
|
||||
const kind = (writeable ? "var" : "const");
|
||||
if (topScope.hasOwn(name)) {
|
||||
topScope.remove(name);
|
||||
}
|
||||
topScope.add(name, kind, {loc: {start: {line: -1}}}, -1);
|
||||
}
|
||||
}
|
||||
|
||||
const topScope = new Scope({
|
||||
kind: "hoist",
|
||||
node: {},
|
||||
parent: null,
|
||||
});
|
||||
|
||||
const complementary = {
|
||||
undefined: false,
|
||||
Infinity: false,
|
||||
console: false,
|
||||
};
|
||||
|
||||
inject(complementary);
|
||||
// inject(jshint_vars.reservedVars);
|
||||
// inject(jshint_vars.ecmaIdentifiers);
|
||||
|
||||
// link it in
|
||||
programScope.parent = topScope;
|
||||
topScope.children.push(programScope);
|
||||
|
||||
return topScope;
|
||||
}
|
||||
|
||||
function isConstLet(kind) {
|
||||
return kind === "const" || kind === "let";
|
||||
}
|
||||
|
||||
function isNonFunctionBlock(node, parent) {
|
||||
return node.type === "BlockStatement" && parent.type !== "FunctionDeclaration" && parent.type !== "FunctionExpression";
|
||||
}
|
||||
|
||||
function isForWithConstLet(node) {
|
||||
return node.type === "ForStatement" && node.init && node.init.type === "VariableDeclaration" && isConstLet(node.init.kind);
|
||||
}
|
||||
|
||||
function isForInOfWithConstLet(node) {
|
||||
return isForInOf(node) && node.left.type === "VariableDeclaration" && isConstLet(node.left.kind);
|
||||
}
|
||||
|
||||
function isForInOf(node) {
|
||||
return node.type === "ForInStatement" || node.type === "ForOfStatement";
|
||||
}
|
||||
|
||||
function isFunction(node) {
|
||||
return node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
|
||||
}
|
||||
|
||||
function isReference(node) {
|
||||
const parent = node.$parent;
|
||||
return node.$refToScope ||
|
||||
node.type === "Identifier" &&
|
||||
!(parent.type === "VariableDeclarator" && parent.id === node) && // var|let|const $
|
||||
!(parent.type === "MemberExpression" && parent.computed === false && parent.property === node) && // obj.$
|
||||
!(parent.type === "Property" && parent.key === node) && // {$: ...}
|
||||
!(parent.type === "LabeledStatement" && parent.label === node) && // $: ...
|
||||
!(parent.type === "CatchClause" && parent.param === node) && // catch($)
|
||||
!(isFunction(parent) && parent.id === node) && // function $(..
|
||||
!(isFunction(parent) && is.someof(node, parent.params)) && // function f($)..
|
||||
true;
|
||||
}
|
||||
@@ -428,3 +428,18 @@ var x = /*@ngInject*/ (function() {
|
||||
return function($a) {
|
||||
};
|
||||
})();
|
||||
|
||||
|
||||
// reference support
|
||||
function MyCtrl1(a, b) {
|
||||
}
|
||||
if (true) {
|
||||
// proper scope analysis including shadowing
|
||||
let MyCtrl1 = function(c) {
|
||||
}
|
||||
angular.module("MyMod").directive("foo", MyCtrl1);
|
||||
}
|
||||
angular.module("MyMod").controller("bar", MyCtrl1);
|
||||
function MyCtrl2(z) {
|
||||
}
|
||||
funcall(/*@ngInject*/ MyCtrl2); // explicit annotation on reference flows back to definition
|
||||
|
||||
@@ -446,3 +446,20 @@ var x = /*@ngInject*/ (function() {
|
||||
return ["$a", function($a) {
|
||||
}];
|
||||
})();
|
||||
|
||||
|
||||
// reference support
|
||||
function MyCtrl1(a, b) {
|
||||
}
|
||||
MyCtrl1.$inject = ["a", "b"];
|
||||
if (true) {
|
||||
// proper scope analysis including shadowing
|
||||
let MyCtrl1 = ["c", function(c) {
|
||||
}]
|
||||
angular.module("MyMod").directive("foo", MyCtrl1);
|
||||
}
|
||||
angular.module("MyMod").controller("bar", MyCtrl1);
|
||||
function MyCtrl2(z) {
|
||||
}
|
||||
MyCtrl2.$inject = ["z"];
|
||||
funcall(/*@ngInject*/ MyCtrl2); // explicit annotation on reference flows back to definition
|
||||
|
||||
@@ -446,3 +446,20 @@ var x = /*@ngInject*/ (function() {
|
||||
return ['$a', function($a) {
|
||||
}];
|
||||
})();
|
||||
|
||||
|
||||
// reference support
|
||||
function MyCtrl1(a, b) {
|
||||
}
|
||||
MyCtrl1.$inject = ['a', 'b'];
|
||||
if (true) {
|
||||
// proper scope analysis including shadowing
|
||||
let MyCtrl1 = ['c', function(c) {
|
||||
}]
|
||||
angular.module("MyMod").directive("foo", MyCtrl1);
|
||||
}
|
||||
angular.module("MyMod").controller("bar", MyCtrl1);
|
||||
function MyCtrl2(z) {
|
||||
}
|
||||
MyCtrl2.$inject = ['z'];
|
||||
funcall(/*@ngInject*/ MyCtrl2); // explicit annotation on reference flows back to definition
|
||||
|
||||
Reference in New Issue
Block a user