initial commit taken from gitlab.lrz.de

This commit is contained in:
privatereese
2018-08-24 18:09:42 +02:00
parent ae54ed4c48
commit fc05486403
28494 changed files with 2159823 additions and 0 deletions

View File

@@ -0,0 +1,410 @@
/**
* @fileoverview Utility class and functions for React components detection
* @author Yannick Croissant
*/
'use strict';
/**
* Components
* @class
*/
function Components() {
this.list = {};
this.getId = function (node) {
return node && node.range.join(':');
};
}
/**
* Add a node to the components list, or update it if it's already in the list
*
* @param {ASTNode} node The AST node being added.
* @param {Number} confidence Confidence in the component detection (0=banned, 1=maybe, 2=yes)
*/
Components.prototype.add = function (node, confidence) {
const id = this.getId(node);
if (this.list[id]) {
if (confidence === 0 || this.list[id].confidence === 0) {
this.list[id].confidence = 0;
} else {
this.list[id].confidence = Math.max(this.list[id].confidence, confidence);
}
return;
}
this.list[id] = {
node: node,
confidence: confidence,
};
};
/**
* Find a component in the list using its node
*
* @param {ASTNode} node The AST node being searched.
* @returns {Object} Component object, undefined if the component is not found
*/
Components.prototype.get = function (node) {
const id = this.getId(node);
return this.list[id];
};
/**
* Update a component in the list
*
* @param {ASTNode} node The AST node being updated.
* @param {Object} props Additional properties to add to the component.
*/
Components.prototype.set = function (node, props) {
let currentNode = node;
while (currentNode && !this.list[this.getId(currentNode)]) {
currentNode = node.parent;
}
if (!currentNode) {
return;
}
const id = this.getId(currentNode);
this.list[id] = Object.assign({}, this.list[id], props);
};
/**
* Return the components list
* Components for which we are not confident are not returned
*
* @returns {Object} Components list
*/
Components.prototype.all = function () {
const list = {};
Object.keys(this.list).forEach((i) => {
if ({}.hasOwnProperty.call(this.list, i) && this.list[i].confidence >= 2) {
list[i] = this.list[i];
}
});
return list;
};
/**
* Return the length of the components list
* Components for which we are not confident are not counted
*
* @returns {Number} Components list length
*/
Components.prototype.length = function () {
let length = 0;
Object.keys(this.list).forEach((i) => {
if ({}.hasOwnProperty.call(this.list, i) && this.list[i].confidence >= 2) {
length += 1;
}
});
return length;
};
function componentRule(rule, context) {
const sourceCode = context.getSourceCode();
const components = new Components();
// Utilities for component detection
const utils = {
/**
* Check if the node is a React ES5 component
*
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if the node is a React ES5 component, false if not
*/
isES5Component: function (node) {
if (!node.parent) {
return false;
}
return /^(React\.)?createClass$/.test(sourceCode.getText(node.parent.callee));
},
/**
* Check if the node is a React ES6 component
*
* @param {ASTNode} node The AST node being checked.
* @returns {Boolean} True if the node is a React ES6 component, false if not
*/
isES6Component: function (node) {
if (!node.superClass) {
return false;
}
return /^(React\.)?(Pure)?Component$/.test(sourceCode.getText(node.superClass));
},
/**
* Check if the node is returning JSX
*
* @param {ASTNode} node The AST node being checked (must be a ReturnStatement).
* @returns {Boolean} True if the node is returning JSX, false if not
*/
isReturningJSX: function (node) {
let property;
switch (node.type) {
case 'ReturnStatement':
property = 'argument';
break;
case 'ArrowFunctionExpression':
property = 'body';
break;
default:
return false;
}
const returnsJSX =
node[property] &&
node[property].type === 'JSXElement'
;
const returnsReactCreateElement =
node[property] &&
node[property].callee &&
node[property].callee.property &&
node[property].callee.property.name === 'createElement'
;
return Boolean(returnsJSX || returnsReactCreateElement);
},
/**
* Get the parent component node from the current scope
*
* @returns {ASTNode} component node, null if we are not in a component
*/
getParentComponent: function () {
return (
utils.getParentES6Component() ||
utils.getParentES5Component() ||
utils.getParentStatelessComponent()
);
},
/**
* Get the parent ES5 component node from the current scope
*
* @returns {ASTNode} component node, null if we are not in a component
*/
getParentES5Component: function () {
let scope = context.getScope();
while (scope) {
const node = scope.block && scope.block.parent && scope.block.parent.parent;
if (node && utils.isES5Component(node)) {
return node;
}
scope = scope.upper;
}
return null;
},
/**
* Get the parent ES6 component node from the current scope
*
* @returns {ASTNode} component node, null if we are not in a component
*/
getParentES6Component: function () {
let scope = context.getScope();
while (scope && scope.type !== 'class') {
scope = scope.upper;
}
const node = scope && scope.block;
if (!node || !utils.isES6Component(node)) {
return null;
}
return node;
},
/**
* Get the parent stateless component node from the current scope
*
* @returns {ASTNode} component node, null if we are not in a component
*/
getParentStatelessComponent: function () {
let scope = context.getScope();
while (scope) {
const node = scope.block;
// Ignore non functions
const isFunction = /Function/.test(node.type);
// Ignore classes methods
const isNotMethod = !node.parent || node.parent.type !== 'MethodDefinition';
// Ignore arguments (callback, etc.)
const isNotArgument = !node.parent || node.parent.type !== 'CallExpression';
if (isFunction && isNotMethod && isNotArgument) {
return node;
}
scope = scope.upper;
}
return null;
},
/**
* Get the related component from a node
*
* @param {ASTNode} node The AST node being checked (must be a MemberExpression).
* @returns {ASTNode} component node, null if we cannot find the component
*/
getRelatedComponent: function (node) {
let currentNode = node;
let i;
let j;
let k;
let l;
// Get the component path
const componentPath = [];
while (currentNode) {
if (currentNode.property && currentNode.property.type === 'Identifier') {
componentPath.push(currentNode.property.name);
}
if (currentNode.object && currentNode.object.type === 'Identifier') {
componentPath.push(currentNode.object.name);
}
currentNode = currentNode.object;
}
componentPath.reverse();
// Find the variable in the current scope
const variableName = componentPath.shift();
if (!variableName) {
return null;
}
let variableInScope;
const variables = context.getScope().variables;
for (i = 0, j = variables.length; i < j; i++) { // eslint-disable-line no-plusplus
if (variables[i].name === variableName) {
variableInScope = variables[i];
break;
}
}
if (!variableInScope) {
return null;
}
// Find the variable declaration
let defInScope;
const defs = variableInScope.defs;
for (i = 0, j = defs.length; i < j; i++) { // eslint-disable-line no-plusplus
if (
defs[i].type === 'ClassName' ||
defs[i].type === 'FunctionName' ||
defs[i].type === 'Variable'
) {
defInScope = defs[i];
break;
}
}
if (!defInScope) {
return null;
}
currentNode = defInScope.node.init || defInScope.node;
// Traverse the node properties to the component declaration
for (i = 0, j = componentPath.length; i < j; i++) { // eslint-disable-line no-plusplus
if (!currentNode.properties) {
continue; // eslint-disable-line no-continue
}
for (k = 0, l = currentNode.properties.length; k < l; k++) { // eslint-disable-line no-plusplus, max-len
if (currentNode.properties[k].key.name === componentPath[i]) {
currentNode = currentNode.properties[k];
break;
}
}
if (!currentNode) {
return null;
}
currentNode = currentNode.value;
}
// Return the component
return components.get(currentNode);
},
};
// Component detection instructions
const detectionInstructions = {
ClassDeclaration: function (node) {
if (!utils.isES6Component(node)) {
return;
}
components.add(node, 2);
},
ClassProperty: function () {
const node = utils.getParentComponent();
if (!node) {
return;
}
components.add(node, 2);
},
ObjectExpression: function (node) {
if (!utils.isES5Component(node)) {
return;
}
components.add(node, 2);
},
FunctionExpression: function () {
const node = utils.getParentComponent();
if (!node) {
return;
}
components.add(node, 1);
},
FunctionDeclaration: function () {
const node = utils.getParentComponent();
if (!node) {
return;
}
components.add(node, 1);
},
ArrowFunctionExpression: function () {
const node = utils.getParentComponent();
if (!node) {
return;
}
if (node.expression && utils.isReturningJSX(node)) {
components.add(node, 2);
} else {
components.add(node, 1);
}
},
ThisExpression: function () {
const node = utils.getParentComponent();
if (!node || !/Function/.test(node.type)) {
return;
}
// Ban functions with a ThisExpression
components.add(node, 0);
},
ReturnStatement: function (node) {
if (!utils.isReturningJSX(node)) {
return;
}
const parentNode = utils.getParentComponent();
if (!parentNode) {
return;
}
components.add(parentNode, 2);
},
};
// Update the provided rule instructions to add the component detection
const ruleInstructions = rule(context, components, utils);
const updatedRuleInstructions = Object.assign({}, ruleInstructions);
Object.keys(detectionInstructions).forEach((instruction) => {
updatedRuleInstructions[instruction] = (node) => {
detectionInstructions[instruction](node);
return ruleInstructions[instruction] ? ruleInstructions[instruction](node) : undefined;
};
});
// Return the updated rule instructions
return updatedRuleInstructions;
}
Components.detect = function (rule) {
return componentRule.bind(this, rule);
};
module.exports = Components;

View File

@@ -0,0 +1,408 @@
'use strict';
/**
* StyleSheets represents the StyleSheets found in the source code.
* @constructor
*/
function StyleSheets() {
this.styleSheets = {};
}
/**
* Add adds a StyleSheet to our StyleSheets collections.
*
* @param {string} styleSheetName - The name of the StyleSheet.
* @param {object} properties - The collection of rules in the styleSheet.
*/
StyleSheets.prototype.add = function (styleSheetName, properties) {
this.styleSheets[styleSheetName] = properties;
};
/**
* MarkAsUsed marks a rule as used in our source code by removing it from the
* specified StyleSheet rules.
*
* @param {string} fullyQualifiedName - The fully qualified name of the rule.
* for example 'styles.text'
*/
StyleSheets.prototype.markAsUsed = function (fullyQualifiedName) {
const nameSplit = fullyQualifiedName.split('.');
const styleSheetName = nameSplit[0];
const styleSheetProperty = nameSplit[1];
if (this.styleSheets[styleSheetName]) {
this.styleSheets[styleSheetName] = this
.styleSheets[styleSheetName]
.filter(property => property.key.name !== styleSheetProperty);
}
};
/**
* GetUnusedReferences returns all collected StyleSheets and their
* unmarked rules.
*/
StyleSheets.prototype.getUnusedReferences = function () {
return this.styleSheets;
};
/**
* AddColorLiterals adds an array of expressions that contain color literals
* to the ColorLiterals collection
* @param {array} expressions - an array of expressions containing color literals
*/
StyleSheets.prototype.addColorLiterals = function (expressions) {
if (!this.colorLiterals) {
this.colorLiterals = [];
}
this.colorLiterals = this.colorLiterals.concat(expressions);
};
/**
* GetColorLiterals returns an array of collected color literals expressions
* @returns {Array}
*/
StyleSheets.prototype.getColorLiterals = function () {
return this.colorLiterals;
};
/**
* AddObjectexpressions adds an array of expressions to the ObjectExpressions collection
* @param {Array} expressions - an array of expressions containing ObjectExpressions in
* inline styles
*/
StyleSheets.prototype.addObjectExpressions = function (expressions) {
if (!this.objectExpressions) {
this.objectExpressions = [];
}
this.objectExpressions = this.objectExpressions.concat(expressions);
};
/**
* GetObjectExpressions returns an array of collected object expressiosn used in inline styles
* @returns {Array}
*/
StyleSheets.prototype.getObjectExpressions = function () {
return this.objectExpressions;
};
let currentContent;
const getSourceCode = node => currentContent
.getSourceCode(node)
.getText(node);
const astHelpers = {
containsStyleSheetObject: function (node) {
return Boolean(
node &&
node.init &&
node.init.callee &&
node.init.callee.object &&
node.init.callee.object.name === 'StyleSheet'
);
},
containsCreateCall: function (node) {
return Boolean(
node &&
node.init &&
node.init.callee &&
node.init.callee.property &&
node.init.callee.property.name === 'create'
);
},
isStyleSheetDeclaration: function (node) {
return Boolean(
astHelpers.containsStyleSheetObject(node) &&
astHelpers.containsCreateCall(node)
);
},
getStyleSheetName: function (node) {
if (
node &&
node.id
) {
return node.id.name;
}
},
getStyleDeclarations: function (node) {
if (
node &&
node.init &&
node.init.arguments &&
node.init.arguments[0] &&
node.init.arguments[0].properties
) {
return node
.init
.arguments[0]
.properties
.filter(property => property.type === 'Property');
}
return [];
},
isStyleAttribute: function (node) {
return Boolean(
node.type === 'JSXAttribute' &&
node.name &&
node.name.name &&
node.name.name.toLowerCase().includes('style')
);
},
collectStyleObjectExpressions: function (node, context) {
currentContent = context;
if (astHelpers.hasArrayOfStyleReferences(node)) {
const styleReferenceContainers = node
.expression
.elements;
return astHelpers.collectStyleObjectExpressionFromContainers(
styleReferenceContainers
);
} else if (node && node.expression) {
return astHelpers.getStyleObjectExpressionFromNode(node.expression);
}
return [];
},
collectColorLiterals: function (node, context) {
if (!node) {
return [];
}
currentContent = context;
if (astHelpers.hasArrayOfStyleReferences(node)) {
const styleReferenceContainers = node
.expression
.elements;
return astHelpers.collectColorLiteralsFromContainers(
styleReferenceContainers
);
}
if (node.type === 'ObjectExpression') {
return astHelpers.getColorLiteralsFromNode(node);
}
return astHelpers.getColorLiteralsFromNode(node.expression);
},
collectStyleObjectExpressionFromContainers: function (nodes) {
let objectExpressions = [];
nodes.forEach((node) => {
objectExpressions = objectExpressions
.concat(astHelpers.getStyleObjectExpressionFromNode(node));
});
return objectExpressions;
},
collectColorLiteralsFromContainers: function (nodes) {
let colorLiterals = [];
nodes.forEach((node) => {
colorLiterals = colorLiterals
.concat(astHelpers.getColorLiteralsFromNode(node));
});
return colorLiterals;
},
getStyleReferenceFromNode: function (node) {
let styleReference;
let leftStyleReferences;
let rightStyleReferences;
if (!node) {
return [];
}
switch (node.type) {
case 'MemberExpression':
styleReference = astHelpers.getStyleReferenceFromExpression(node);
return [styleReference];
case 'LogicalExpression':
leftStyleReferences = astHelpers.getStyleReferenceFromNode(node.left);
rightStyleReferences = astHelpers.getStyleReferenceFromNode(node.right);
return [].concat(leftStyleReferences).concat(rightStyleReferences);
case 'ConditionalExpression':
leftStyleReferences = astHelpers.getStyleReferenceFromNode(node.consequent);
rightStyleReferences = astHelpers.getStyleReferenceFromNode(node.alternate);
return [].concat(leftStyleReferences).concat(rightStyleReferences);
default:
return [];
}
},
getStyleObjectExpressionFromNode: function (node) {
let leftStyleObjectExpression;
let rightStyleObjectExpression;
if (!node) {
return [];
}
if (node.type === 'ObjectExpression') {
return [astHelpers.getStyleObjectFromExpression(node)];
}
switch (node.type) {
case 'LogicalExpression':
leftStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.left);
rightStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.right);
return [].concat(leftStyleObjectExpression).concat(rightStyleObjectExpression);
case 'ConditionalExpression':
leftStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.consequent);
rightStyleObjectExpression = astHelpers.getStyleObjectExpressionFromNode(node.alternate);
return [].concat(leftStyleObjectExpression).concat(rightStyleObjectExpression);
default:
return [];
}
},
getColorLiteralsFromNode: function (node) {
let leftColorLiterals;
let rightColorLiterals;
if (!node) {
return [];
}
if (node.type === 'ObjectExpression') {
return [astHelpers.getColorLiteralsFromExpression(node)];
}
switch (node.type) {
case 'LogicalExpression':
leftColorLiterals = astHelpers.getColorLiteralsFromNode(node.left);
rightColorLiterals = astHelpers.getColorLiteralsFromNode(node.right);
return [].concat(leftColorLiterals).concat(rightColorLiterals);
case 'ConditionalExpression':
leftColorLiterals = astHelpers.getColorLiteralsFromNode(node.consequent);
rightColorLiterals = astHelpers.getColorLiteralsFromNode(node.alternate);
return [].concat(leftColorLiterals).concat(rightColorLiterals);
default:
return [];
}
},
hasArrayOfStyleReferences: function (node) {
return node && Boolean(
node.type === 'JSXExpressionContainer' &&
node.expression &&
node.expression.type === 'ArrayExpression'
);
},
getStyleReferenceFromExpression: function (node) {
const result = [];
const name = astHelpers.getObjectName(node);
if (name) {
result.push(name);
}
const property = astHelpers.getPropertyName(node);
if (property) {
result.push(property);
}
return result.join('.');
},
getStyleObjectFromExpression: function (node) {
const obj = {};
let invalid = false;
if (node.properties && node.properties.length) {
node.properties.forEach((p) => {
if (!p.value || !p.key) {
return;
}
if (p.value.type === 'Literal') {
invalid = true;
obj[p.key.name] = p.value.value;
} else if (p.value.type === 'ConditionalExpression') {
const innerNode = p.value;
if (innerNode.consequent.type === 'Literal' || innerNode.alternate.type === 'Literal') {
invalid = true;
obj[p.key.name] = getSourceCode(innerNode);
}
} else if (p.value.type === 'UnaryExpression' && p.value.operator === '-') {
invalid = true;
obj[p.key.name] = -1 * p.value.argument.value;
} else if (p.value.type === 'UnaryExpression' && p.value.operator === '+') {
invalid = true;
obj[p.key.name] = p.value.argument.value;
}
});
}
return invalid ? { expression: obj, node: node } : undefined;
},
getColorLiteralsFromExpression: function (node) {
const obj = {};
let invalid = false;
if (node.properties && node.properties.length) {
node.properties.forEach((p) => {
if (p.key && p.key.name && p.key.name.toLowerCase().indexOf('color') !== -1) {
if (p.value.type === 'Literal') {
invalid = true;
obj[p.key.name] = p.value.value;
} else if (p.value.type === 'ConditionalExpression') {
const innerNode = p.value;
if (innerNode.consequent.type === 'Literal' || innerNode.alternate.type === 'Literal') {
invalid = true;
obj[p.key.name] = getSourceCode(innerNode);
}
}
}
});
}
return invalid ? { expression: obj, node: node } : undefined;
},
getObjectName: function (node) {
if (
node &&
node.object &&
node.object.name
) {
return node.object.name;
}
},
getPropertyName: function (node) {
if (
node &&
node.property &&
node.property.name
) {
return node.property.name;
}
},
getPotentialStyleReferenceFromMemberExpression: function (node) {
if (
node &&
node.object &&
node.object.type === 'Identifier' &&
node.object.name &&
node.property &&
node.property.type === 'Identifier' &&
node.property.name &&
node.parent.type !== 'MemberExpression'
) {
return [node.object.name, node.property.name].join('.');
}
},
};
module.exports.astHelpers = astHelpers;
module.exports.StyleSheets = StyleSheets;

View File

@@ -0,0 +1,92 @@
/**
* @fileoverview Utility functions for React components detection
* @author Yannick Croissant
*/
'use strict';
/**
* Record that a particular variable has been used in code
*
* @param {String} name The name of the variable to mark as used.
* @returns {Boolean} True if the variable was found and marked as used, false if not.
*/
function markVariableAsUsed(context, name) {
let scope = context.getScope();
let variables;
let i;
let len;
let found = false;
// Special Node.js scope means we need to start one level deeper
if (scope.type === 'global') {
while (scope.childScopes.length) {
scope = scope.childScopes[0];
}
}
do {
variables = scope.variables;
for (i = 0, len = variables.length; i < len; i++) { // eslint-disable-line no-plusplus
if (variables[i].name === name) {
variables[i].eslintUsed = true;
found = true;
}
}
scope = scope.upper;
} while (scope);
return found;
}
/**
* Search a particular variable in a list
* @param {Array} variables The variables list.
* @param {Array} name The name of the variable to search.
* @returns {Boolean} True if the variable was found, false if not.
*/
function findVariable(variables, name) {
let i;
let len;
for (i = 0, len = variables.length; i < len; i++) { // eslint-disable-line no-plusplus
if (variables[i].name === name) {
return true;
}
}
return false;
}
/**
* List all variable in a given scope
*
* Contain a patch for babel-eslint to avoid https://github.com/babel/babel-eslint/issues/21
*
* @param {Object} context The current rule context.
* @param {Array} name The name of the variable to search.
* @returns {Boolean} True if the variable was found, false if not.
*/
function variablesInScope(context) {
let scope = context.getScope();
let variables = scope.variables;
while (scope.type !== 'global') {
scope = scope.upper;
variables = scope.variables.concat(variables);
}
if (scope.childScopes.length) {
variables = scope.childScopes[0].variables.concat(variables);
if (scope.childScopes[0].childScopes.length) {
variables = scope.childScopes[0].childScopes[0].variables.concat(variables);
}
}
return variables;
}
module.exports = {
findVariable: findVariable,
variablesInScope: variablesInScope,
markVariableAsUsed: markVariableAsUsed,
};