@spinnaker/eslint-plugin 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintignore +3 -0
- package/CHANGELOG.md +77 -0
- package/LICENSE.txt +203 -0
- package/README.md +259 -0
- package/babel.config.js +3 -0
- package/base.config.js +86 -0
- package/create-rule.js +67 -0
- package/eslint-plugin.ts +47 -0
- package/index.js +6 -0
- package/newrule.sh +88 -0
- package/none.config.js +18 -0
- package/package.json +51 -0
- package/rules/api-deprecation.spec.ts +79 -0
- package/rules/api-deprecation.ts +254 -0
- package/rules/api-no-slashes.spec.ts +84 -0
- package/rules/api-no-slashes.ts +148 -0
- package/rules/api-no-unused-chaining.spec.ts +26 -0
- package/rules/api-no-unused-chaining.ts +47 -0
- package/rules/import-from-alias-not-npm.spec.ts +22 -0
- package/rules/import-from-alias-not-npm.ts +53 -0
- package/rules/import-from-npm-not-alias.spec.ts +23 -0
- package/rules/import-from-npm-not-alias.ts +56 -0
- package/rules/import-from-npm-not-relative.spec.ts +22 -0
- package/rules/import-from-npm-not-relative.ts +57 -0
- package/rules/import-from-presentation-not-core.spec.ts +44 -0
- package/rules/import-from-presentation-not-core.ts +106 -0
- package/rules/import-relative-within-subpackage.spec.ts +50 -0
- package/rules/import-relative-within-subpackage.ts +71 -0
- package/rules/import-sort.spec.ts +85 -0
- package/rules/import-sort.ts +280 -0
- package/rules/migrate-to-mock-http-client.spec.ts +78 -0
- package/rules/migrate-to-mock-http-client.ts +122 -0
- package/rules/ng-no-component-class.spec.ts +45 -0
- package/rules/ng-no-component-class.ts +68 -0
- package/rules/ng-no-module-export.spec.ts +26 -0
- package/rules/ng-no-module-export.ts +117 -0
- package/rules/ng-no-require-angularjs.spec.ts +27 -0
- package/rules/ng-no-require-angularjs.ts +94 -0
- package/rules/ng-no-require-module-deps.spec.ts +33 -0
- package/rules/ng-no-require-module-deps.ts +211 -0
- package/rules/ng-strictdi.spec.ts +100 -0
- package/rules/ng-strictdi.ts +304 -0
- package/rules/prefer-promise-like.spec.ts +75 -0
- package/rules/prefer-promise-like.ts +108 -0
- package/rules/react2angular-with-error-boundary.spec.ts +29 -0
- package/rules/react2angular-with-error-boundary.ts +118 -0
- package/rules/rest-prefer-static-strings-in-initializer.spec.ts +34 -0
- package/rules/rest-prefer-static-strings-in-initializer.ts +89 -0
- package/template/template-rule.spec.ts +17 -0
- package/template/template-rule.ts +21 -0
- package/test.eslintrc +20 -0
- package/test_rule_against_deck_source.sh +18 -0
- package/tsconfig.json +17 -0
- package/utils/angular-rule/angular-rule.js +302 -0
- package/utils/angular-rule/false-values.js +6 -0
- package/utils/angular-rule/utils.js +624 -0
- package/utils/import-aliases.mock.ts +17 -0
- package/utils/import-aliases.ts +91 -0
- package/utils/mockModule.js +15 -0
- package/utils/ruleTester.js +8 -0
- package/utils/utils.ts +90 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import { flattenDeep, memoize } from 'lodash';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
export function locateSourceFile(modulesPath: string, moduleName: string, importPath = '') {
|
|
6
|
+
const srcPrefixes = ['src', ''];
|
|
7
|
+
const indexFiles = ['', 'index'];
|
|
8
|
+
const extensions = ['.ts', '.tsx', '.js', '.jsx'];
|
|
9
|
+
|
|
10
|
+
const paths = srcPrefixes.map((prefix) =>
|
|
11
|
+
extensions.map((extension) =>
|
|
12
|
+
indexFiles.map((indexFile) => {
|
|
13
|
+
return path.join(modulesPath, moduleName, prefix, importPath, indexFile) + extension;
|
|
14
|
+
}),
|
|
15
|
+
),
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
return flattenDeep<string>(paths).find((p) => fs.existsSync(p));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function _getAllSpinnakerPackages(modulesPath: string) {
|
|
22
|
+
const paths = fs.readdirSync(modulesPath);
|
|
23
|
+
return paths
|
|
24
|
+
.map((file) => path.join(modulesPath, file))
|
|
25
|
+
.filter((child) => fs.statSync(child).isDirectory())
|
|
26
|
+
.map((packagePath) => packagePath.split('/').pop());
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export const getAllSpinnakerPackages = memoize(_getAllSpinnakerPackages);
|
|
30
|
+
|
|
31
|
+
function makeResult(pkg: string, importPath: string) {
|
|
32
|
+
const subPkg = getSubPackage(pkg, importPath);
|
|
33
|
+
importPath = importPath || '';
|
|
34
|
+
const importPathWithSlash = importPath ? '/' + importPath : '';
|
|
35
|
+
return pkg ? { pkg, subPkg, importPath, importPathWithSlash } : undefined;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Given '@spinnaker/amazon', returns { pkg: 'amazon', path: undefined };
|
|
40
|
+
* Given '@spinnaker/core/deep/import', returns { pkg: 'core', path: 'deep/import' };
|
|
41
|
+
* Given 'anythingelse', returns undefined
|
|
42
|
+
*/
|
|
43
|
+
export function getImportFromNpm(importString: string) {
|
|
44
|
+
const regexp = new RegExp(`^@spinnaker/([^/]+)(/.*)?$`);
|
|
45
|
+
const [, pkg, importPath] = regexp.exec(importString) || [];
|
|
46
|
+
return makeResult(pkg, importPath);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* If code imports from a known spinnaker package alias
|
|
51
|
+
* Given 'amazon', returns { pkg: 'amazon', path: undefined };
|
|
52
|
+
* Given 'core/deep/import', returns { pkg: 'core', path: 'deep/import' };
|
|
53
|
+
* Given 'nonspinnakerpackage/deep/import', returns undefined
|
|
54
|
+
*/
|
|
55
|
+
export function getAliasImport(allSpinnakerPackages: string[], importString: string) {
|
|
56
|
+
const [, pkg, importPath] = /^([^/]+)\/(.*)$/.exec(importString) || [];
|
|
57
|
+
return allSpinnakerPackages.includes(pkg) ? makeResult(pkg, importPath) : undefined;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* If code imports from .. relatively, returns the potential alias
|
|
62
|
+
* Assume all examples are from a file /packages/core/subdir/file.ts
|
|
63
|
+
* Given '../../amazon/loadbalancers/loadbalancer', returns { pkg: 'amazon', path: 'loadbalancers/loadbalancer' };
|
|
64
|
+
* Given '../widgets/button', returns { pkg: 'core', path: 'widgets/button' };
|
|
65
|
+
* Given './file2', returns { pkg: 'core', path: 'subdir/file2' };
|
|
66
|
+
*/
|
|
67
|
+
export function getRelativeImport(sourceFileName: string, modulesPath: string, importString: string) {
|
|
68
|
+
if (!importString.startsWith('../')) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const resolvedPath = path.resolve(sourceFileName, importString);
|
|
73
|
+
const maybeImport = path.relative(modulesPath, resolvedPath);
|
|
74
|
+
const [pkg, ...rest] = maybeImport.split(path.sep);
|
|
75
|
+
|
|
76
|
+
return pkg ? makeResult(pkg, rest.join('/')) : undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function _getSourceFileDetails(sourceFile: string) {
|
|
80
|
+
const [, modulesPath, ownPackage, filePath] = /^(.*packages)\/([^/]+)\/(?:src\/)?(.*)$/.exec(sourceFile) || [];
|
|
81
|
+
const ownSubPackage = getSubPackage(ownPackage, filePath);
|
|
82
|
+
const sourceDirectory = path.resolve(sourceFile, '..');
|
|
83
|
+
return { modulesPath, sourceDirectory, ownPackage, ownSubPackage, filePath };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getSubPackage(_packageName: string, filePath: string) {
|
|
87
|
+
const [, subPkg] = /^([^/]+)\/?.*/.exec(filePath) || [];
|
|
88
|
+
return subPkg;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export const getSourceFileDetails = memoize(_getSourceFileDetails);
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/* eslint-disable no-undef */
|
|
2
|
+
function mockModule(moduleName) {
|
|
3
|
+
jest.mock(moduleName);
|
|
4
|
+
const mockedModule = require(moduleName);
|
|
5
|
+
const actualModule = jest.requireActual(moduleName);
|
|
6
|
+
for (const [key, value] of Object.entries(actualModule)) {
|
|
7
|
+
if (typeof value === 'function') {
|
|
8
|
+
mockedModule[key].mockImplementation(value);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
return require(moduleName);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = mockModule;
|
package/utils/utils.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import type { Rule, Scope } from 'eslint';
|
|
2
|
+
import type {
|
|
3
|
+
CallExpression,
|
|
4
|
+
Expression,
|
|
5
|
+
Identifier,
|
|
6
|
+
Literal,
|
|
7
|
+
MemberExpression,
|
|
8
|
+
NewExpression,
|
|
9
|
+
Node,
|
|
10
|
+
Program,
|
|
11
|
+
SpreadElement,
|
|
12
|
+
} from 'estree';
|
|
13
|
+
import * as _ from 'lodash/fp';
|
|
14
|
+
|
|
15
|
+
export const getNodeType = (obj: Node) => obj?.type;
|
|
16
|
+
export const isType = <T extends Node>(type: string) => (obj: Node): obj is T => getNodeType(obj) === type;
|
|
17
|
+
export const isIdentifier = isType<Identifier>('Identifier');
|
|
18
|
+
export const isCallExpression = isType<CallExpression>('CallExpression');
|
|
19
|
+
export const isMemberExpression = isType<MemberExpression>('MemberExpression');
|
|
20
|
+
export const isLiteral = isType<Literal>('Literal');
|
|
21
|
+
export const isNewExpression = isType<NewExpression>('NewExpression');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Recursively grab the callee until an Identifier is found.
|
|
25
|
+
*
|
|
26
|
+
* API.all().all().one('foo/bar');
|
|
27
|
+
*
|
|
28
|
+
* var calleeOne = ...
|
|
29
|
+
* getCallingIdentifier(calleeOne).name === 'API'
|
|
30
|
+
*/
|
|
31
|
+
export function getCallingIdentifier(calleeObject: Node): Identifier {
|
|
32
|
+
if (isIdentifier(calleeObject)) {
|
|
33
|
+
return calleeObject;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (isCallExpression(calleeObject)) {
|
|
37
|
+
const target = isMemberExpression(calleeObject.callee) ? calleeObject.callee.object : calleeObject.callee;
|
|
38
|
+
return getCallingIdentifier(target);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getCallingIdentifierName(calleeObject: Node) {
|
|
45
|
+
return getCallingIdentifier(calleeObject)?.name;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* given an identifier, finds its Variable in the enclosing scope
|
|
50
|
+
*/
|
|
51
|
+
export function getVariableInScope(context: Rule.RuleContext, identifier: Identifier): Scope.Variable {
|
|
52
|
+
if (!isIdentifier(identifier)) {
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const { references } = context.getScope();
|
|
57
|
+
const ref = references.find((r) => r.identifier.name === identifier.name);
|
|
58
|
+
return ref ? ref.resolved : undefined;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const getVariableInitializer = _.get('defs[0].node.init');
|
|
62
|
+
|
|
63
|
+
export function getProgram(node: Node): Program {
|
|
64
|
+
let _node = node as Node & Rule.NodeParentExtension;
|
|
65
|
+
while (_node.parent) {
|
|
66
|
+
if (_node.parent.type === 'Program') {
|
|
67
|
+
return _node.parent;
|
|
68
|
+
}
|
|
69
|
+
_node = _node.parent;
|
|
70
|
+
}
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Given a CallExpression: API.one().two().three().get();
|
|
76
|
+
* Returns an array of the chained CallExpressions: [.one(), .two(), .three(), .get()]
|
|
77
|
+
*/
|
|
78
|
+
export const getCallChain = (node: Node): CallExpression[] => {
|
|
79
|
+
if (isCallExpression(node) && isMemberExpression(node.callee) && isCallExpression(node.callee.object)) {
|
|
80
|
+
return getCallChain(node.callee.object).concat(node);
|
|
81
|
+
} else if (isCallExpression(node)) {
|
|
82
|
+
return [node];
|
|
83
|
+
}
|
|
84
|
+
return [];
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
export function getArgsText(context: Rule.RuleContext, args: Array<Expression | SpreadElement>) {
|
|
88
|
+
const sourceCode = context.getSourceCode();
|
|
89
|
+
return (args || []).map((arg) => sourceCode.getText(arg)).join(', ');
|
|
90
|
+
}
|