ternlint.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722
  1. /* eslint-disable */
  2. import infer from "tern/lib/infer"
  3. import tern from "tern/lib/tern"
  4. const walk = require("acorn-walk")
  5. var defaultRules = {
  6. UnknownProperty: { severity: "warning" },
  7. UnknownIdentifier: { severity: "warning" },
  8. NotAFunction: { severity: "error" },
  9. InvalidArgument: { severity: "error" },
  10. UnusedVariable: { severity: "warning" },
  11. UnknownModule: { severity: "error" },
  12. MixedReturnTypes: { severity: "warning" },
  13. ObjectLiteral: { severity: "error" },
  14. TypeMismatch: { severity: "warning" },
  15. Array: { severity: "error" },
  16. ES6Modules: { severity: "error" },
  17. }
  18. function makeVisitors(server, query, file, messages) {
  19. function addMessage(node, msg, severity) {
  20. var error = makeError(node, msg, severity)
  21. messages.push(error)
  22. }
  23. function makeError(node, msg, severity) {
  24. var pos = getPosition(node)
  25. var error = {
  26. message: msg,
  27. from: tern.outputPos(query, file, pos.start),
  28. to: tern.outputPos(query, file, pos.end),
  29. severity: severity,
  30. }
  31. if (query.lineNumber) {
  32. error.lineNumber = query.lineCharPositions
  33. ? error.from.line
  34. : tern.outputPos({ lineCharPositions: true }, file, pos.start).line
  35. }
  36. if (!query.groupByFiles) error.file = file.name
  37. return error
  38. }
  39. function getNodeName(node) {
  40. if (node.callee) {
  41. // This is a CallExpression node.
  42. // We get the position of the function name.
  43. return getNodeName(node.callee)
  44. } else if (node.property) {
  45. // This is a MemberExpression node.
  46. // We get the name of the property.
  47. return node.property.name
  48. } else {
  49. return node.name
  50. }
  51. }
  52. function getNodeValue(node) {
  53. if (node.callee) {
  54. // This is a CallExpression node.
  55. // We get the position of the function name.
  56. return getNodeValue(node.callee)
  57. } else if (node.property) {
  58. // This is a MemberExpression node.
  59. // We get the value of the property.
  60. return node.property.value
  61. } else {
  62. if (node.type === "Identifier") {
  63. var query = { type: "definition", start: node.start, end: node.end }
  64. var expr = tern.findQueryExpr(file, query)
  65. var type = infer.expressionType(expr)
  66. var objExpr = type.getType()
  67. if (objExpr && objExpr.originNode)
  68. return getNodeValue(objExpr.originNode)
  69. return null
  70. }
  71. return node.value
  72. }
  73. }
  74. function getPosition(node) {
  75. if (node.callee) {
  76. // This is a CallExpression node.
  77. // We get the position of the function name.
  78. return getPosition(node.callee)
  79. }
  80. if (node.property) {
  81. // This is a MemberExpression node.
  82. // We get the position of the property.
  83. return node.property
  84. }
  85. return node
  86. }
  87. function getTypeName(type) {
  88. if (!type) return "Unknown type"
  89. if (type.types) {
  90. // multiple types
  91. var types = type.types,
  92. s = ""
  93. for (var i = 0; i < types.length; i++) {
  94. if (i > 0) s += "|"
  95. var t = getTypeName(types[i])
  96. if (t != "Unknown type") s += t
  97. }
  98. return s == "" ? "Unknown type" : s
  99. }
  100. if (type.name) {
  101. return type.name
  102. }
  103. return type.proto ? type.proto.name : "Unknown type"
  104. }
  105. function hasProto(expectedType, name) {
  106. if (!expectedType) return false
  107. if (!expectedType.proto) return false
  108. return expectedType.proto.name === name
  109. }
  110. function isRegexExpected(expectedType) {
  111. return hasProto(expectedType, "RegExp.prototype")
  112. }
  113. function isEmptyType(val) {
  114. return !val || (val.types && val.types.length == 0)
  115. }
  116. function compareType(expected, actual) {
  117. if (isEmptyType(expected) || isEmptyType(actual)) return true
  118. if (expected.types) {
  119. for (var i = 0; i < expected.types.length; i++) {
  120. if (actual.types) {
  121. for (var j = 0; j < actual.types.length; j++) {
  122. if (compareType(expected.types[i], actual.types[j])) return true
  123. }
  124. } else {
  125. if (compareType(expected.types[i], actual.getType())) return true
  126. }
  127. }
  128. return false
  129. } else if (actual.types) {
  130. for (var i = 0; i < actual.types.length; i++) {
  131. if (compareType(expected.getType(), actual.types[i])) return true
  132. }
  133. }
  134. var expectedType = expected.getType(),
  135. actualType = actual.getType()
  136. if (!expectedType || !actualType) return true
  137. var currentProto = actualType.proto
  138. while (currentProto) {
  139. if (expectedType.proto && expectedType.proto.name === currentProto.name)
  140. return true
  141. currentProto = currentProto.proto
  142. }
  143. return false
  144. }
  145. function checkPropsInObject(node, expectedArg, actualObj, invalidArgument) {
  146. var properties = node.properties,
  147. expectedObj = expectedArg.getType()
  148. for (var i = 0; i < properties.length; i++) {
  149. var property = properties[i],
  150. key = property.key,
  151. prop = key && key.name,
  152. value = property.value
  153. if (prop) {
  154. var expectedType = expectedObj.hasProp(prop)
  155. if (!expectedType) {
  156. // key doesn't exists
  157. addMessage(
  158. key,
  159. "Invalid property at " +
  160. (i + 1) +
  161. ": " +
  162. prop +
  163. " is not a property in " +
  164. getTypeName(expectedArg),
  165. invalidArgument.severity
  166. )
  167. } else {
  168. // test that each object literal prop is the correct type
  169. var actualType = actualObj.props[prop]
  170. if (!compareType(expectedType, actualType)) {
  171. addMessage(
  172. value,
  173. "Invalid property at " +
  174. (i + 1) +
  175. ": cannot convert from " +
  176. getTypeName(actualType) +
  177. " to " +
  178. getTypeName(expectedType),
  179. invalidArgument.severity
  180. )
  181. }
  182. }
  183. }
  184. }
  185. }
  186. function checkItemInArray(node, expectedArg, state, invalidArgument) {
  187. var elements = node.elements,
  188. expectedType = expectedArg.hasProp("<i>")
  189. for (var i = 0; i < elements.length; i++) {
  190. var elt = elements[i],
  191. actualType = infer.expressionType({ node: elt, state: state })
  192. if (!compareType(expectedType, actualType)) {
  193. addMessage(
  194. elt,
  195. "Invalid item at " +
  196. (i + 1) +
  197. ": cannot convert from " +
  198. getTypeName(actualType) +
  199. " to " +
  200. getTypeName(expectedType),
  201. invalidArgument.severity
  202. )
  203. }
  204. }
  205. }
  206. function isObjectLiteral(type) {
  207. var objType = type.getObjType()
  208. return objType && objType.proto && objType.proto.name == "Object.prototype"
  209. }
  210. function getFunctionLint(fnType) {
  211. if (fnType.lint) return fnType.lint
  212. if (fnType.metaData) {
  213. fnType.lint = getLint(fnType.metaData["!lint"])
  214. return fnType.lint
  215. }
  216. }
  217. function isFunctionType(type) {
  218. if (type.types) {
  219. for (var i = 0; i < type.types.length; i++) {
  220. if (isFunctionType(type.types[i])) return true
  221. }
  222. }
  223. return type.proto && type.proto.name == "Function.prototype"
  224. }
  225. function validateCallExpression(node, state, c) {
  226. var notAFunctionRule = getRule("NotAFunction"),
  227. invalidArgument = getRule("InvalidArgument")
  228. if (!notAFunctionRule && !invalidArgument) return
  229. var type = infer.expressionType({ node: node.callee, state: state })
  230. if (!type.isEmpty()) {
  231. // If type.isEmpty(), it is handled by MemberExpression/Identifier already.
  232. // An expression can have multiple possible (guessed) types.
  233. // If one of them is a function, type.getFunctionType() will return it.
  234. var fnType = type.getFunctionType()
  235. if (fnType == null) {
  236. if (notAFunctionRule && !isFunctionType(type))
  237. addMessage(
  238. node,
  239. "'" + getNodeName(node) + "' is not a function",
  240. notAFunctionRule.severity
  241. )
  242. return
  243. }
  244. var fnLint = getFunctionLint(fnType)
  245. var continueLint = fnLint ? fnLint(node, addMessage, getRule) : true
  246. if (continueLint && fnType.args) {
  247. // validate parameters of the function
  248. if (!invalidArgument) return
  249. var actualArgs = node.arguments
  250. if (!actualArgs) return
  251. var expectedArgs = fnType.args
  252. for (var i = 0; i < expectedArgs.length; i++) {
  253. var expectedArg = expectedArgs[i]
  254. if (actualArgs.length > i) {
  255. var actualNode = actualArgs[i]
  256. if (isRegexExpected(expectedArg.getType())) {
  257. var value = getNodeValue(actualNode)
  258. if (value) {
  259. try {
  260. var regex = new RegExp(value)
  261. } catch (e) {
  262. addMessage(
  263. actualNode,
  264. "Invalid argument at " + (i + 1) + ": " + e,
  265. invalidArgument.severity
  266. )
  267. }
  268. }
  269. } else {
  270. var actualArg = infer.expressionType({
  271. node: actualNode,
  272. state: state,
  273. })
  274. // if actual type is an Object literal and expected type is an object, we ignore
  275. // the comparison type since object literal properties validation is done inside "ObjectExpression".
  276. if (!(expectedArg.getObjType() && isObjectLiteral(actualArg))) {
  277. if (!compareType(expectedArg, actualArg)) {
  278. addMessage(
  279. actualNode,
  280. "Invalid argument at " +
  281. (i + 1) +
  282. ": cannot convert from " +
  283. getTypeName(actualArg) +
  284. " to " +
  285. getTypeName(expectedArg),
  286. invalidArgument.severity
  287. )
  288. }
  289. }
  290. }
  291. }
  292. }
  293. }
  294. }
  295. }
  296. function validateAssignement(nodeLeft, nodeRight, rule, state) {
  297. if (!nodeLeft || !nodeRight) return
  298. if (!rule) return
  299. var leftType = infer.expressionType({ node: nodeLeft, state: state }),
  300. rightType = infer.expressionType({ node: nodeRight, state: state })
  301. if (!compareType(leftType, rightType)) {
  302. addMessage(
  303. nodeRight,
  304. "Type mismatch: cannot convert from " +
  305. getTypeName(leftType) +
  306. " to " +
  307. getTypeName(rightType),
  308. rule.severity
  309. )
  310. }
  311. }
  312. function validateDeclaration(node, state, c) {
  313. function isUsedVariable(varNode, varState, file, srv) {
  314. var name = varNode.name
  315. for (
  316. var scope = varState;
  317. scope && !(name in scope.props);
  318. scope = scope.prev
  319. ) {}
  320. if (!scope) return false
  321. var hasRef = false
  322. function searchRef(file) {
  323. return function (node, scopeHere) {
  324. if (node != varNode) {
  325. hasRef = true
  326. throw new Error() // throw an error to stop the search.
  327. }
  328. }
  329. }
  330. try {
  331. if (scope.node) {
  332. // local scope
  333. infer.findRefs(scope.node, scope, name, scope, searchRef(file))
  334. } else {
  335. // global scope
  336. infer.findRefs(file.ast, file.scope, name, scope, searchRef(file))
  337. for (var i = 0; i < srv.files.length && !hasRef; ++i) {
  338. var cur = srv.files[i]
  339. if (cur != file)
  340. infer.findRefs(cur.ast, cur.scope, name, scope, searchRef(cur))
  341. }
  342. }
  343. } catch (e) {}
  344. return hasRef
  345. }
  346. var unusedRule = getRule("UnusedVariable"),
  347. mismatchRule = getRule("TypeMismatch")
  348. if (!unusedRule && !mismatchRule) return
  349. switch (node.type) {
  350. case "VariableDeclaration":
  351. for (var i = 0; i < node.declarations.length; ++i) {
  352. var decl = node.declarations[i],
  353. varNode = decl.id
  354. if (varNode.name != "✖") {
  355. // unused variable
  356. if (unusedRule && !isUsedVariable(varNode, state, file, server))
  357. addMessage(
  358. varNode,
  359. "Unused variable '" + getNodeName(varNode) + "'",
  360. unusedRule.severity
  361. )
  362. // type mismatch?
  363. if (mismatchRule)
  364. validateAssignement(varNode, decl.init, mismatchRule, state)
  365. }
  366. }
  367. break
  368. case "FunctionDeclaration":
  369. if (unusedRule) {
  370. var varNode = node.id
  371. if (
  372. varNode.name != "✖" &&
  373. !isUsedVariable(varNode, state, file, server)
  374. )
  375. addMessage(
  376. varNode,
  377. "Unused function '" + getNodeName(varNode) + "'",
  378. unusedRule.severity
  379. )
  380. }
  381. break
  382. }
  383. }
  384. function getArrType(type) {
  385. if (type instanceof infer.Arr) {
  386. return type.getObjType()
  387. } else if (type.types) {
  388. for (var i = 0; i < type.types.length; i++) {
  389. if (getArrType(type.types[i])) return type.types[i]
  390. }
  391. }
  392. }
  393. var visitors = {
  394. VariableDeclaration: validateDeclaration,
  395. FunctionDeclaration: validateDeclaration,
  396. ReturnStatement: function (node, state, c) {
  397. if (!node.argument) return
  398. var rule = getRule("MixedReturnTypes")
  399. if (!rule) return
  400. if (state.fnType && state.fnType.retval) {
  401. var actualType = infer.expressionType({
  402. node: node.argument,
  403. state: state,
  404. }),
  405. expectedType = state.fnType.retval
  406. if (!compareType(expectedType, actualType)) {
  407. addMessage(
  408. node,
  409. "Invalid return type : cannot convert from " +
  410. getTypeName(actualType) +
  411. " to " +
  412. getTypeName(expectedType),
  413. rule.severity
  414. )
  415. }
  416. }
  417. },
  418. // Detects expressions of the form `object.property`
  419. MemberExpression: function (node, state, c) {
  420. var rule = getRule("UnknownProperty")
  421. if (!rule) return
  422. var prop = node.property && node.property.name
  423. if (!prop || prop == "✖") return
  424. var type = infer.expressionType({ node: node, state: state })
  425. var parentType = infer.expressionType({ node: node.object, state: state })
  426. if (node.computed) {
  427. // Bracket notation.
  428. // Until we figure out how to handle these properly, we ignore these nodes.
  429. return
  430. }
  431. if (!parentType.isEmpty() && type.isEmpty()) {
  432. // The type of the property cannot be determined, which means
  433. // that the property probably doesn't exist.
  434. // We only do this check if the parent type is known,
  435. // otherwise we will generate errors for an entire chain of unknown
  436. // properties.
  437. // Also, the expression may be valid even if the parent type is unknown,
  438. // since the inference engine cannot detect the type in all cases.
  439. var propertyDefined = false
  440. // In some cases the type is unknown, even if the property is defined
  441. if (parentType.types) {
  442. // We cannot use parentType.hasProp or parentType.props - in the case of an AVal,
  443. // this may contain properties that are not really defined.
  444. parentType.types.forEach(function (potentialType) {
  445. // Obj#hasProp checks the prototype as well
  446. if (
  447. typeof potentialType.hasProp == "function" &&
  448. potentialType.hasProp(prop, true)
  449. ) {
  450. propertyDefined = true
  451. }
  452. })
  453. }
  454. if (!propertyDefined) {
  455. addMessage(
  456. node,
  457. "Unknown property '" + getNodeName(node) + "'",
  458. rule.severity
  459. )
  460. }
  461. }
  462. },
  463. // Detects top-level identifiers, e.g. the object in
  464. // `object.property` or just `object`.
  465. Identifier: function (node, state, c) {
  466. var rule = getRule("UnknownIdentifier")
  467. if (!rule) return
  468. var type = infer.expressionType({ node: node, state: state })
  469. if (type.originNode != null || type.origin != null) {
  470. // The node is defined somewhere (could be this node),
  471. // regardless of whether or not the type is known.
  472. } else if (type.isEmpty()) {
  473. // The type of the identifier cannot be determined,
  474. // and the origin is unknown.
  475. addMessage(
  476. node,
  477. "Unknown identifier '" + getNodeName(node) + "'",
  478. rule.severity
  479. )
  480. } else {
  481. // Even though the origin node is unknown, the type is known.
  482. // This is typically the case for built-in identifiers (e.g. window or document).
  483. }
  484. },
  485. // Detects function calls.
  486. // `node.callee` is the expression (Identifier or MemberExpression)
  487. // the is called as a function.
  488. NewExpression: validateCallExpression,
  489. CallExpression: validateCallExpression,
  490. AssignmentExpression: function (node, state, c) {
  491. var rule = getRule("TypeMismatch")
  492. validateAssignement(node.left, node.right, rule, state)
  493. },
  494. ObjectExpression: function (node, state, c) {
  495. // validate properties of the object literal
  496. var rule = getRule("ObjectLiteral")
  497. if (!rule) return
  498. var actualType = node.objType
  499. var ctxType = infer.typeFromContext(file.ast, {
  500. node: node,
  501. state: state,
  502. }),
  503. expectedType = null
  504. if (ctxType instanceof infer.Obj) {
  505. expectedType = ctxType.getObjType()
  506. } else if (ctxType && ctxType.makeupType) {
  507. var objType = ctxType.makeupType()
  508. if (objType && objType.getObjType()) {
  509. expectedType = objType.getObjType()
  510. }
  511. }
  512. if (expectedType && expectedType != actualType) {
  513. // expected type is known. Ex: config object of RequireJS
  514. checkPropsInObject(node, expectedType, actualType, rule)
  515. }
  516. },
  517. ArrayExpression: function (node, state, c) {
  518. // validate elements of the Arrray
  519. var rule = getRule("Array")
  520. if (!rule) return
  521. //var actualType = infer.expressionType({node: node, state: state});
  522. var ctxType = infer.typeFromContext(file.ast, {
  523. node: node,
  524. state: state,
  525. }),
  526. expectedType = getArrType(ctxType)
  527. if (expectedType /*&& expectedType != actualType*/) {
  528. // expected type is known. Ex: config object of RequireJS
  529. checkItemInArray(node, expectedType, state, rule)
  530. }
  531. },
  532. ImportDeclaration: function (node, state, c) {
  533. // Validate ES6 modules from + specifiers
  534. var rule = getRule("ES6Modules")
  535. if (!rule) return
  536. var me = infer.cx().parent.mod.modules
  537. if (!me) return // tern plugin modules.js is not loaded
  538. var source = node.source
  539. if (!source) return
  540. // Validate ES6 modules "from"
  541. var modType = me.getModType(source)
  542. if (!modType) {
  543. addMessage(
  544. source,
  545. "Invalid modules from '" + source.value + "'",
  546. rule.severity
  547. )
  548. return
  549. }
  550. // Validate ES6 modules "specifiers"
  551. var specifiers = node.specifiers,
  552. specifier
  553. if (!specifiers) return
  554. for (var i = 0; i < specifiers.length; i++) {
  555. var specifier = specifiers[i],
  556. imported = specifier.imported
  557. if (imported) {
  558. var name = imported.name
  559. if (!modType.hasProp(name))
  560. addMessage(
  561. imported,
  562. "Invalid modules specifier '" + getNodeName(imported) + "'",
  563. rule.severity
  564. )
  565. }
  566. }
  567. },
  568. }
  569. return visitors
  570. }
  571. // Adapted from infer.searchVisitor.
  572. // Record the scope and pass it through in the state.
  573. // VariableDeclaration in infer.searchVisitor breaks things for us.
  574. var scopeVisitor = walk.make({
  575. Function: function (node, _st, c) {
  576. var scope = node.scope
  577. if (node.id) c(node.id, scope)
  578. for (var i = 0; i < node.params.length; ++i) c(node.params[i], scope)
  579. c(node.body, scope, "ScopeBody")
  580. },
  581. Statement: function (node, st, c) {
  582. c(node, node.scope || st)
  583. },
  584. })
  585. // Validate one file
  586. export function validateFile(server, query, file) {
  587. try {
  588. var messages = [],
  589. ast = file.ast,
  590. state = file.scope
  591. var visitors = makeVisitors(server, query, file, messages)
  592. walk.simple(ast, visitors, infer.searchVisitor, state)
  593. return { messages: messages }
  594. } catch (e) {
  595. console.error(e.stack)
  596. return { messages: [] }
  597. }
  598. }
  599. export function registerTernLinter() {
  600. tern.defineQueryType("lint", {
  601. takesFile: true,
  602. run: function (server, query, file) {
  603. return validateFile(server, query, file)
  604. },
  605. })
  606. tern.defineQueryType("lint-full", {
  607. run: function (server, query) {
  608. return validateFiles(server, query)
  609. },
  610. })
  611. tern.registerPlugin("lint", function (server, options) {
  612. server._lint = {
  613. rules: getRules(options),
  614. }
  615. return {
  616. passes: {},
  617. loadFirst: true,
  618. }
  619. })
  620. }
  621. // Validate the whole files of the server
  622. export function validateFiles(server, query) {
  623. try {
  624. var messages = [],
  625. files = server.files,
  626. groupByFiles = query.groupByFiles == true
  627. for (var i = 0; i < files.length; ++i) {
  628. var messagesFile = groupByFiles ? [] : messages,
  629. file = files[i],
  630. ast = file.ast,
  631. state = file.scope
  632. var visitors = makeVisitors(server, query, file, messagesFile)
  633. walk.simple(ast, visitors, infer.searchVisitor, state)
  634. if (groupByFiles)
  635. messages.push({ file: file.name, messages: messagesFile })
  636. }
  637. return { messages: messages }
  638. } catch (e) {
  639. console.error(e.stack)
  640. return { messages: [] }
  641. }
  642. }
  643. var lints = Object.create(null)
  644. var getLint = (tern.getLint = function (name) {
  645. if (!name) return null
  646. return lints[name]
  647. })
  648. function getRules(options) {
  649. var rules = {}
  650. for (var ruleName in defaultRules) {
  651. if (
  652. options &&
  653. options.rules &&
  654. options.rules[ruleName] &&
  655. options.rules[ruleName].severity
  656. ) {
  657. if (options.rules[ruleName].severity != "none")
  658. rules[ruleName] = options.rules[ruleName]
  659. } else {
  660. rules[ruleName] = defaultRules[ruleName]
  661. }
  662. }
  663. return rules
  664. }
  665. function getRule(ruleName) {
  666. const cx = infer.cx()
  667. const server = cx.parent
  668. const rules =
  669. server && server._lint && server._lint.rules
  670. ? server._lint.rules
  671. : defaultRules
  672. return rules[ruleName]
  673. }