nginject.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. // nginject.js
  2. // MIT licensed, see LICENSE file
  3. // Copyright (c) 2013-2015 Olov Lassus <olov.lassus@gmail.com>
  4. "use strict";
  5. const is = require("simple-is");
  6. module.exports = {
  7. inspectComments: inspectComments,
  8. inspectNode: inspectNode,
  9. };
  10. function inspectNode(node, ctx) {
  11. if (node.type === "CallExpression") {
  12. inspectCallExpression(node, ctx);
  13. } else if (node.type === "FunctionExpression" || node.type === "FunctionDeclaration") {
  14. inspectFunction(node, ctx);
  15. }
  16. }
  17. function inspectCallExpression(node, ctx) {
  18. const name = node.callee.name;
  19. if (node.callee.type === "Identifier" && (name === "ngInject" || name === "ngNoInject") && node.arguments.length === 1) {
  20. const block = (name === "ngNoInject");
  21. addSuspect(node.arguments[0], ctx, block);
  22. }
  23. }
  24. const ngAnnotatePrologueDirectives = ["ngInject", "ngNoInject"];
  25. function inspectFunction(node, ctx) {
  26. const str = matchPrologueDirectives(ngAnnotatePrologueDirectives, node);
  27. if (!str) {
  28. return;
  29. }
  30. const block = (str === "ngNoInject");
  31. // now add the correct suspect
  32. // for function declarations, it is always the function declaration node itself
  33. if (node.type === "FunctionDeclaration") {
  34. addSuspect(node, ctx, block);
  35. return;
  36. }
  37. // node is a function expression below
  38. // case 1: a function expression which is the rhs of a variable declarator, such as
  39. // var f1 = function(a) {
  40. // "ngInject"
  41. // };
  42. // in this case we can mark the declarator, same as saying var /*@ngInject*/ f1 = function(a) ..
  43. // or /*@ngInject*/ var f1 = function(a) ..
  44. // f1.$inject = ["a"]; will be added (or rebuilt/removed)
  45. if (node.$parent.type === "VariableDeclarator") {
  46. addSuspect(node.$parent, ctx, block);
  47. return;
  48. }
  49. // case 2: an anonymous function expression, such as
  50. // g(function(a) {
  51. // "ngInject"
  52. // });
  53. //
  54. // the suspect is now its parent annotated array (if any), otherwise itself
  55. // there is a risk of false suspects here, in case the parent annotated array has nothing to do
  56. // with annotations. the risk should be very low and hopefully easy to workaround
  57. //
  58. // added/rebuilt/removed => g(["a", function(a) {
  59. // "ngInject"
  60. // }]);
  61. const maybeArrayExpression = node.$parent;
  62. if (ctx.isAnnotatedArray(maybeArrayExpression)) {
  63. addSuspect(maybeArrayExpression, ctx, block);
  64. } else {
  65. addSuspect(node, ctx, block);
  66. }
  67. }
  68. function matchPrologueDirectives(prologueDirectives, node) {
  69. const body = node.body.body;
  70. let found = null;
  71. for (let i = 0; i < body.length; i++) {
  72. if (body[i].type !== "ExpressionStatement") {
  73. break;
  74. }
  75. const expr = body[i].expression;
  76. const isStringLiteral = (expr.type === "Literal" && typeof expr.value === "string");
  77. if (!isStringLiteral) {
  78. break;
  79. }
  80. if (prologueDirectives.indexOf(expr.value) >= 0) {
  81. found = expr.value;
  82. break;
  83. }
  84. }
  85. return found;
  86. }
  87. function inspectComments(ctx) {
  88. const comments = ctx.comments;
  89. for (let i = 0; i < comments.length; i++) {
  90. const comment = comments[i];
  91. const yesPos = comment.value.indexOf("@ngInject");
  92. const noPos = (yesPos === -1 ? comment.value.indexOf("@ngNoInject") : -1);
  93. if (yesPos === -1 && noPos === -1) {
  94. continue;
  95. }
  96. const target = ctx.lut.findNodeFromPos(comment.range[1]);
  97. if (!target) {
  98. continue;
  99. }
  100. addSuspect(target, ctx, noPos >= 0);
  101. }
  102. }
  103. function addSuspect(target, ctx, block) {
  104. if (target.type === "ObjectExpression") {
  105. // /*@ngInject*/ {f1: function(a), .., {f2: function(b)}}
  106. addObjectExpression(target, ctx);
  107. } else if (target.type === "AssignmentExpression" && target.right.type === "ObjectExpression") {
  108. // /*@ngInject*/ f(x.y = {f1: function(a), .., {f2: function(b)}})
  109. addObjectExpression(target.right, ctx);
  110. } else if (target.type === "ExpressionStatement" && target.expression.type === "AssignmentExpression" && target.expression.right.type === "ObjectExpression") {
  111. // /*@ngInject*/ x.y = {f1: function(a), .., {f2: function(b)}}
  112. addObjectExpression(target.expression.right, ctx);
  113. } else if (target.type === "VariableDeclaration" && target.declarations.length === 1 && target.declarations[0].init && target.declarations[0].init.type === "ObjectExpression") {
  114. // /*@ngInject*/ var x = {f1: function(a), .., {f2: function(b)}}
  115. addObjectExpression(target.declarations[0].init, ctx);
  116. } else if (target.type === "Property") {
  117. // {/*@ngInject*/ justthisone: function(a), ..}
  118. target.value.$limitToMethodName = "*never*";
  119. addOrBlock(target.value, ctx);
  120. } else {
  121. // /*@ngInject*/ function(a) {}
  122. target.$limitToMethodName = "*never*";
  123. addOrBlock(target, ctx);
  124. }
  125. function addObjectExpression(node, ctx) {
  126. nestedObjectValues(node).forEach(function(n) {
  127. n.$limitToMethodName = "*never*";
  128. addOrBlock(n, ctx);
  129. });
  130. }
  131. function addOrBlock(node, ctx) {
  132. if (block) {
  133. ctx.blocked.push(node);
  134. } else {
  135. ctx.addModuleContextIndependentSuspect(node, ctx)
  136. }
  137. }
  138. }
  139. function nestedObjectValues(node, res) {
  140. res = res || [];
  141. node.properties.forEach(function(prop) {
  142. const v = prop.value;
  143. if (is.someof(v.type, ["FunctionExpression", "ArrayExpression"])) {
  144. res.push(v);
  145. } else if (v.type === "ObjectExpression") {
  146. nestedObjectValues(v, res);
  147. }
  148. });
  149. return res;
  150. }