source: imaps-frontend/node_modules/eslint-plugin-react/lib/util/componentUtil.js

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 4.7 KB
Line 
1'use strict';
2
3const doctrine = require('doctrine');
4const pragmaUtil = require('./pragma');
5const eslintUtil = require('./eslint');
6
7const getScope = eslintUtil.getScope;
8const getSourceCode = eslintUtil.getSourceCode;
9const getText = eslintUtil.getText;
10
11// eslint-disable-next-line valid-jsdoc
12/**
13 * @template {(_: object) => any} T
14 * @param {T} fn
15 * @returns {T}
16 */
17function memoize(fn) {
18 const cache = new WeakMap();
19 // @ts-ignore
20 return function memoizedFn(arg) {
21 const cachedValue = cache.get(arg);
22 if (cachedValue !== undefined) {
23 return cachedValue;
24 }
25 const v = fn(arg);
26 cache.set(arg, v);
27 return v;
28 };
29}
30
31const getPragma = memoize(pragmaUtil.getFromContext);
32const getCreateClass = memoize(pragmaUtil.getCreateClassFromContext);
33
34/**
35 * @param {ASTNode} node
36 * @param {Context} context
37 * @returns {boolean}
38 */
39function isES5Component(node, context) {
40 const pragma = getPragma(context);
41 const createClass = getCreateClass(context);
42
43 if (!node.parent || !node.parent.callee) {
44 return false;
45 }
46 const callee = node.parent.callee;
47 // React.createClass({})
48 if (callee.type === 'MemberExpression') {
49 return callee.object.name === pragma && callee.property.name === createClass;
50 }
51 // createClass({})
52 if (callee.type === 'Identifier') {
53 return callee.name === createClass;
54 }
55 return false;
56}
57
58/**
59 * Check if the node is explicitly declared as a descendant of a React Component
60 * @param {any} node
61 * @param {Context} context
62 * @returns {boolean}
63 */
64function isExplicitComponent(node, context) {
65 const sourceCode = getSourceCode(context);
66 let comment;
67 // Sometimes the passed node may not have been parsed yet by eslint, and this function call crashes.
68 // Can be removed when eslint sets "parent" property for all nodes on initial AST traversal: https://github.com/eslint/eslint-scope/issues/27
69 // eslint-disable-next-line no-warning-comments
70 // FIXME: Remove try/catch when https://github.com/eslint/eslint-scope/issues/27 is implemented.
71 try {
72 comment = sourceCode.getJSDocComment(node);
73 } catch (e) {
74 comment = null;
75 }
76
77 if (comment === null) {
78 return false;
79 }
80
81 let commentAst;
82 try {
83 commentAst = doctrine.parse(comment.value, {
84 unwrap: true,
85 tags: ['extends', 'augments'],
86 });
87 } catch (e) {
88 // handle a bug in the archived `doctrine`, see #2596
89 return false;
90 }
91
92 const relevantTags = commentAst.tags.filter((tag) => tag.name === 'React.Component' || tag.name === 'React.PureComponent');
93
94 return relevantTags.length > 0;
95}
96
97/**
98 * @param {ASTNode} node
99 * @param {Context} context
100 * @returns {boolean}
101 */
102function isES6Component(node, context) {
103 const pragma = getPragma(context);
104 if (isExplicitComponent(node, context)) {
105 return true;
106 }
107
108 if (!node.superClass) {
109 return false;
110 }
111 if (node.superClass.type === 'MemberExpression') {
112 return node.superClass.object.name === pragma
113 && /^(Pure)?Component$/.test(node.superClass.property.name);
114 }
115 if (node.superClass.type === 'Identifier') {
116 return /^(Pure)?Component$/.test(node.superClass.name);
117 }
118 return false;
119}
120
121/**
122 * Get the parent ES5 component node from the current scope
123 * @param {Context} context
124 * @param {ASTNode} node
125 * @returns {ASTNode|null}
126 */
127function getParentES5Component(context, node) {
128 let scope = getScope(context, node);
129 while (scope) {
130 // @ts-ignore
131 node = scope.block && scope.block.parent && scope.block.parent.parent;
132 if (node && isES5Component(node, context)) {
133 return node;
134 }
135 scope = scope.upper;
136 }
137 return null;
138}
139
140/**
141 * Get the parent ES6 component node from the current scope
142 * @param {Context} context
143 * @param {ASTNode} node
144 * @returns {ASTNode | null}
145 */
146function getParentES6Component(context, node) {
147 let scope = getScope(context, node);
148 while (scope && scope.type !== 'class') {
149 scope = scope.upper;
150 }
151 node = scope && scope.block;
152 if (!node || !isES6Component(node, context)) {
153 return null;
154 }
155 return node;
156}
157
158/**
159 * Checks if a component extends React.PureComponent
160 * @param {ASTNode} node
161 * @param {Context} context
162 * @returns {boolean}
163 */
164function isPureComponent(node, context) {
165 const pragma = getPragma(context);
166 if (node.superClass) {
167 return new RegExp(`^(${pragma}\\.)?PureComponent$`).test(getText(context, node.superClass));
168 }
169 return false;
170}
171
172/**
173 * @param {ASTNode} node
174 * @returns {boolean}
175 */
176function isStateMemberExpression(node) {
177 return node.type === 'MemberExpression' && node.object.type === 'ThisExpression' && node.property.name === 'state';
178}
179
180module.exports = {
181 isES5Component,
182 isES6Component,
183 getParentES5Component,
184 getParentES6Component,
185 isExplicitComponent,
186 isPureComponent,
187 isStateMemberExpression,
188};
Note: See TracBrowser for help on using the repository browser.