123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182 |
- 'use strict';
- const doctrine = require('doctrine');
- const pragmaUtil = require('./pragma');
- // eslint-disable-next-line valid-jsdoc
- /**
- * @template {(_: object) => any} T
- * @param {T} fn
- * @returns {T}
- */
- function memoize(fn) {
- const cache = new WeakMap();
- // @ts-ignore
- return function memoizedFn(arg) {
- const cachedValue = cache.get(arg);
- if (cachedValue !== undefined) {
- return cachedValue;
- }
- const v = fn(arg);
- cache.set(arg, v);
- return v;
- };
- }
- const getPragma = memoize(pragmaUtil.getFromContext);
- const getCreateClass = memoize(pragmaUtil.getCreateClassFromContext);
- /**
- * @param {ASTNode} node
- * @param {Context} context
- * @returns {boolean}
- */
- function isES5Component(node, context) {
- const pragma = getPragma(context);
- const createClass = getCreateClass(context);
- if (!node.parent || !node.parent.callee) {
- return false;
- }
- const callee = node.parent.callee;
- // React.createClass({})
- if (callee.type === 'MemberExpression') {
- return callee.object.name === pragma && callee.property.name === createClass;
- }
- // createClass({})
- if (callee.type === 'Identifier') {
- return callee.name === createClass;
- }
- return false;
- }
- /**
- * Check if the node is explicitly declared as a descendant of a React Component
- * @param {any} node
- * @param {Context} context
- * @returns {boolean}
- */
- function isExplicitComponent(node, context) {
- const sourceCode = context.getSourceCode();
- let comment;
- // Sometimes the passed node may not have been parsed yet by eslint, and this function call crashes.
- // Can be removed when eslint sets "parent" property for all nodes on initial AST traversal: https://github.com/eslint/eslint-scope/issues/27
- // eslint-disable-next-line no-warning-comments
- // FIXME: Remove try/catch when https://github.com/eslint/eslint-scope/issues/27 is implemented.
- try {
- comment = sourceCode.getJSDocComment(node);
- } catch (e) {
- comment = null;
- }
- if (comment === null) {
- return false;
- }
- let commentAst;
- try {
- commentAst = doctrine.parse(comment.value, {
- unwrap: true,
- tags: ['extends', 'augments'],
- });
- } catch (e) {
- // handle a bug in the archived `doctrine`, see #2596
- return false;
- }
- const relevantTags = commentAst.tags.filter((tag) => tag.name === 'React.Component' || tag.name === 'React.PureComponent');
- return relevantTags.length > 0;
- }
- /**
- * @param {ASTNode} node
- * @param {Context} context
- * @returns {boolean}
- */
- function isES6Component(node, context) {
- const pragma = getPragma(context);
- if (isExplicitComponent(node, context)) {
- return true;
- }
- if (!node.superClass) {
- return false;
- }
- if (node.superClass.type === 'MemberExpression') {
- return node.superClass.object.name === pragma
- && /^(Pure)?Component$/.test(node.superClass.property.name);
- }
- if (node.superClass.type === 'Identifier') {
- return /^(Pure)?Component$/.test(node.superClass.name);
- }
- return false;
- }
- /**
- * Get the parent ES5 component node from the current scope
- * @param {Context} context
- * @returns {ASTNode|null}
- */
- function getParentES5Component(context) {
- let scope = context.getScope();
- while (scope) {
- // @ts-ignore
- const node = scope.block && scope.block.parent && scope.block.parent.parent;
- if (node && isES5Component(node, context)) {
- return node;
- }
- scope = scope.upper;
- }
- return null;
- }
- /**
- * Get the parent ES6 component node from the current scope
- * @param {Context} context
- * @returns {ASTNode | null}
- */
- function getParentES6Component(context) {
- let scope = context.getScope();
- while (scope && scope.type !== 'class') {
- scope = scope.upper;
- }
- const node = scope && scope.block;
- if (!node || !isES6Component(node, context)) {
- return null;
- }
- return node;
- }
- /**
- * Checks if a component extends React.PureComponent
- * @param {ASTNode} node
- * @param {Context} context
- * @returns {boolean}
- */
- function isPureComponent(node, context) {
- const pragma = getPragma(context);
- const sourceCode = context.getSourceCode();
- if (node.superClass) {
- return new RegExp(`^(${pragma}\\.)?PureComponent$`).test(sourceCode.getText(node.superClass));
- }
- return false;
- }
- /**
- * @param {ASTNode} node
- * @returns {boolean}
- */
- function isStateMemberExpression(node) {
- return node.type === 'MemberExpression' && node.object.type === 'ThisExpression' && node.property.name === 'state';
- }
- module.exports = {
- isES5Component,
- isES6Component,
- getParentES5Component,
- getParentES6Component,
- isExplicitComponent,
- isPureComponent,
- isStateMemberExpression,
- };
|