| /** |
| * @fileoverview Disallow undeclared variables in JSX |
| * @author Yannick Croissant |
| */ |
| |
| 'use strict'; |
| |
| const docsUrl = require('../util/docsUrl'); |
| const jsxUtil = require('../util/jsx'); |
| |
| // ------------------------------------------------------------------------------ |
| // Rule Definition |
| // ------------------------------------------------------------------------------ |
| |
| module.exports = { |
| meta: { |
| docs: { |
| description: 'Disallow undeclared variables in JSX', |
| category: 'Possible Errors', |
| recommended: true, |
| url: docsUrl('jsx-no-undef') |
| }, |
| schema: [{ |
| type: 'object', |
| properties: { |
| allowGlobals: { |
| type: 'boolean' |
| } |
| }, |
| additionalProperties: false |
| }] |
| }, |
| |
| create(context) { |
| const config = context.options[0] || {}; |
| const allowGlobals = config.allowGlobals || false; |
| |
| /** |
| * Compare an identifier with the variables declared in the scope |
| * @param {ASTNode} node - Identifier or JSXIdentifier node |
| * @returns {void} |
| */ |
| function checkIdentifierInJSX(node) { |
| let scope = context.getScope(); |
| const sourceCode = context.getSourceCode(); |
| const sourceType = sourceCode.ast.sourceType; |
| let variables = scope.variables; |
| let scopeType = 'global'; |
| let i; |
| let len; |
| |
| // Ignore 'this' keyword (also maked as JSXIdentifier when used in JSX) |
| if (node.name === 'this') { |
| return; |
| } |
| |
| if (!allowGlobals && sourceType === 'module') { |
| scopeType = 'module'; |
| } |
| |
| while (scope.type !== scopeType) { |
| scope = scope.upper; |
| variables = scope.variables.concat(variables); |
| } |
| if (scope.childScopes.length) { |
| variables = scope.childScopes[0].variables.concat(variables); |
| // Temporary fix for babel-eslint |
| if (scope.childScopes[0].childScopes.length) { |
| variables = scope.childScopes[0].childScopes[0].variables.concat(variables); |
| } |
| } |
| |
| for (i = 0, len = variables.length; i < len; i++) { |
| if (variables[i].name === node.name) { |
| return; |
| } |
| } |
| |
| context.report({ |
| node, |
| message: `'${node.name}' is not defined.` |
| }); |
| } |
| |
| return { |
| JSXOpeningElement(node) { |
| switch (node.name.type) { |
| case 'JSXIdentifier': |
| if (jsxUtil.isDOMComponent(node)) { |
| return; |
| } |
| node = node.name; |
| break; |
| case 'JSXMemberExpression': |
| node = node.name; |
| do { |
| node = node.object; |
| } while (node && node.type !== 'JSXIdentifier'); |
| break; |
| case 'JSXNamespacedName': |
| node = node.name.namespace; |
| break; |
| default: |
| break; |
| } |
| checkIdentifierInJSX(node); |
| } |
| }; |
| } |
| }; |