@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,108 @@
|
|
|
1
|
+
import type { TSESTree } from '@typescript-eslint/types';
|
|
2
|
+
import type { Rule } from 'eslint';
|
|
3
|
+
import type { ImportDeclaration } from 'estree';
|
|
4
|
+
import _ from 'lodash';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* No slashes in string literals passed to API.one() / API.all()
|
|
8
|
+
*
|
|
9
|
+
* @version 0.1.0
|
|
10
|
+
* @category
|
|
11
|
+
*/
|
|
12
|
+
const rule = function (context: Rule.RuleContext) {
|
|
13
|
+
return {
|
|
14
|
+
TSTypeReference: function (_node) {
|
|
15
|
+
const node = _node as TSESTree.TSTypeReference;
|
|
16
|
+
// var foo: IPromise<any> = bar()
|
|
17
|
+
// ^^^^^^^^
|
|
18
|
+
const type_IPromise = {
|
|
19
|
+
type: 'TSTypeReference',
|
|
20
|
+
typeName: {
|
|
21
|
+
type: 'Identifier',
|
|
22
|
+
name: 'IPromise',
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// var foo: ng.IPromise<any> = bar()
|
|
27
|
+
// ^^^^^^^^^^^
|
|
28
|
+
const type_ng_IPromise = {
|
|
29
|
+
type: 'TSTypeReference',
|
|
30
|
+
typeName: {
|
|
31
|
+
type: 'TSQualifiedName',
|
|
32
|
+
left: {
|
|
33
|
+
type: 'Identifier',
|
|
34
|
+
name: 'ng',
|
|
35
|
+
},
|
|
36
|
+
right: {
|
|
37
|
+
type: 'Identifier',
|
|
38
|
+
name: 'IPromise',
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const message = `Prefer using PromiseLike type instead of AngularJS IPromise.`;
|
|
44
|
+
const fix = (fixer) => fixer.replaceText(node.typeName, 'PromiseLike');
|
|
45
|
+
if (_.isMatch(node, type_IPromise)) {
|
|
46
|
+
context.report({ fix, node: node.typeName as Rule.Node, message });
|
|
47
|
+
} else if (_.isMatch(node, type_ng_IPromise)) {
|
|
48
|
+
context.report({ fix, node: node.typeName as Rule.Node, message });
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
// If there are any unused IPromise imports, remove them
|
|
53
|
+
ImportDeclaration: function (node: ImportDeclaration) {
|
|
54
|
+
const importIPromise = {
|
|
55
|
+
type: 'ImportSpecifier',
|
|
56
|
+
imported: {
|
|
57
|
+
type: 'Identifier',
|
|
58
|
+
name: 'IPromise',
|
|
59
|
+
},
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const message = `Unused IPromise import`;
|
|
63
|
+
|
|
64
|
+
// import { foo, IPromise, bar } from 'angular';
|
|
65
|
+
// ^^^^^^^^
|
|
66
|
+
const specifiers = node.specifiers || [];
|
|
67
|
+
const foundIPromiseImport = specifiers.find((s) => _.isMatch(s, importIPromise));
|
|
68
|
+
|
|
69
|
+
const variables = context.getScope().variables;
|
|
70
|
+
const variable = variables.find((x) => x.defs.some((def) => def.node === foundIPromiseImport));
|
|
71
|
+
const unused = variable && variable.references.length === 0;
|
|
72
|
+
|
|
73
|
+
const fix = (fixer: Rule.RuleFixer) => {
|
|
74
|
+
const importCount = node.specifiers.length;
|
|
75
|
+
if (importCount === 1) {
|
|
76
|
+
// Delete the whole import
|
|
77
|
+
return fixer.replaceText(node, '');
|
|
78
|
+
} else {
|
|
79
|
+
// Delete only IPromise from the import
|
|
80
|
+
const source = context
|
|
81
|
+
.getSourceCode()
|
|
82
|
+
.getText(node)
|
|
83
|
+
.replace(/,\s*IPromise/g, '')
|
|
84
|
+
.replace(/IPromise\s*,\s*/g, '');
|
|
85
|
+
|
|
86
|
+
return fixer.replaceText(node, source);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
if (foundIPromiseImport && unused) {
|
|
91
|
+
context.report({ node, message, fix });
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const ruleModule: Rule.RuleModule = {
|
|
98
|
+
meta: {
|
|
99
|
+
type: 'problem',
|
|
100
|
+
docs: {
|
|
101
|
+
description: ``,
|
|
102
|
+
},
|
|
103
|
+
fixable: 'code',
|
|
104
|
+
},
|
|
105
|
+
create: rule,
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
export default ruleModule;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import ruleTester from '../utils/ruleTester';
|
|
2
|
+
import rule from './react2angular-with-error-boundary';
|
|
3
|
+
const errorMessage = `Wrap react2angular components in an error boundary using 'withErrorBoundary()'`;
|
|
4
|
+
|
|
5
|
+
ruleTester.run('api-no-slashes', rule, {
|
|
6
|
+
valid: [
|
|
7
|
+
{
|
|
8
|
+
code: `react2angular(withErrorBoundary(MyComponent), ['foo', 'bar']);`,
|
|
9
|
+
},
|
|
10
|
+
],
|
|
11
|
+
|
|
12
|
+
invalid: [
|
|
13
|
+
{
|
|
14
|
+
code: `react2angular(MyComponent, ['foo', 'bar']);`,
|
|
15
|
+
errors: [errorMessage],
|
|
16
|
+
output: `import { withErrorBoundary } from '@spinnaker/core';\nreact2angular(withErrorBoundary(MyComponent, 'react2angular component'), ['foo', 'bar']);`,
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
{
|
|
20
|
+
errors: [errorMessage],
|
|
21
|
+
code: `import { SpinnakerContainer } from '@spinnaker/core';
|
|
22
|
+
module(SPINNAKER_CONTAINER_COMPONENT, []).component('spinnakerContainer',
|
|
23
|
+
react2angular(SpinnakerContainer, ['authenticating', 'routing']));`,
|
|
24
|
+
output: `import { SpinnakerContainer, withErrorBoundary } from '@spinnaker/core';
|
|
25
|
+
module(SPINNAKER_CONTAINER_COMPONENT, []).component('spinnakerContainer',
|
|
26
|
+
react2angular(withErrorBoundary(SpinnakerContainer, 'spinnakerContainer'), ['authenticating', 'routing']));`,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
});
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import type { Rule } from 'eslint';
|
|
2
|
+
import type { CallExpression, ImportDeclaration } from 'estree';
|
|
3
|
+
import _ from 'lodash';
|
|
4
|
+
import { isLiteral } from '../utils/utils';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* react2angular: Always wrap react components in an error boundary
|
|
8
|
+
* Uses withErrorBoundary from core/presentation
|
|
9
|
+
* @version 0.1.0
|
|
10
|
+
*/
|
|
11
|
+
const rule = function (context: Rule.RuleContext) {
|
|
12
|
+
let coreImport: ImportDeclaration;
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
// Find an import from @spinnaker/core or core/presentation
|
|
16
|
+
// This will be used to add the import for withErrorBoundary
|
|
17
|
+
ImportDeclaration: function (node: ImportDeclaration) {
|
|
18
|
+
// import { foo, bar } from 'package';
|
|
19
|
+
// ^^^^^^^
|
|
20
|
+
const from = node.source.value || '';
|
|
21
|
+
if (from === '@spinnaker/core') {
|
|
22
|
+
coreImport = node;
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
CallExpression: function (node: CallExpression & Rule.NodeParentExtension) {
|
|
26
|
+
// Find:
|
|
27
|
+
// react2angular(SomeComponent, ...)
|
|
28
|
+
const match = {
|
|
29
|
+
type: 'CallExpression',
|
|
30
|
+
callee: {
|
|
31
|
+
type: 'Identifier',
|
|
32
|
+
name: 'react2angular',
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
if (!_.isMatch(node, match)) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const r2aComponent = node.arguments[0];
|
|
41
|
+
|
|
42
|
+
const wrappedInErrorBoundaryMatch = {
|
|
43
|
+
type: 'CallExpression',
|
|
44
|
+
callee: {
|
|
45
|
+
type: 'Identifier',
|
|
46
|
+
name: 'withErrorBoundary',
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// The react2angular component is already wrapped, nice!
|
|
51
|
+
if (_.isMatch(r2aComponent, wrappedInErrorBoundaryMatch)) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const message = `Wrap react2angular components in an error boundary using 'withErrorBoundary()'`;
|
|
56
|
+
const filename = context.getFilename();
|
|
57
|
+
const originalComponentSrc = context.getSourceCode().getText(r2aComponent);
|
|
58
|
+
|
|
59
|
+
// Try to determine the angularjs component name
|
|
60
|
+
// Look for component('angularComponentName', react2angular(ReactComponent, ....))
|
|
61
|
+
// ^^^^^^^^^^^^^^^^^^^^^^
|
|
62
|
+
|
|
63
|
+
const parentMatch = {
|
|
64
|
+
type: 'CallExpression',
|
|
65
|
+
callee: {
|
|
66
|
+
property: {
|
|
67
|
+
type: 'Identifier',
|
|
68
|
+
name: 'component',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
const isComponentCallExpression = (node): node is CallExpression => _.isMatch(node, parentMatch);
|
|
73
|
+
|
|
74
|
+
let componentName = `'react2angular component'`;
|
|
75
|
+
const parentNode = node.parent;
|
|
76
|
+
if (isComponentCallExpression(parentNode)) {
|
|
77
|
+
const [componentNameLiteralNode, arg2] = parentNode.arguments || [];
|
|
78
|
+
if (arg2 === node && isLiteral(componentNameLiteralNode)) {
|
|
79
|
+
componentName = componentNameLiteralNode.raw;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const fix = (fixer: Rule.RuleFixer) => {
|
|
84
|
+
const wrapped = `withErrorBoundary(${originalComponentSrc}, ${componentName})`;
|
|
85
|
+
const insertErrorBoundary = fixer.replaceText(r2aComponent, wrapped);
|
|
86
|
+
|
|
87
|
+
const fixes = [insertErrorBoundary];
|
|
88
|
+
|
|
89
|
+
if (coreImport && coreImport.specifiers.length > 0) {
|
|
90
|
+
// Append to the existing core/presentation or @spinnaker/core import
|
|
91
|
+
const lastImport = coreImport.specifiers[coreImport.specifiers.length - 1];
|
|
92
|
+
fixes.push(fixer.insertTextAfter(lastImport, `, withErrorBoundary`));
|
|
93
|
+
} else {
|
|
94
|
+
const importString = filename.includes('/modules/core/')
|
|
95
|
+
? 'core/presentation/SpinErrorBoundary'
|
|
96
|
+
: '@spinnaker/core';
|
|
97
|
+
fixes.push(fixer.insertTextBeforeRange([0, 0], `import { withErrorBoundary } from '${importString}';\n`));
|
|
98
|
+
}
|
|
99
|
+
return fixes;
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
context.report({ message, node, fix });
|
|
103
|
+
},
|
|
104
|
+
};
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const ruleModule: Rule.RuleModule = {
|
|
108
|
+
meta: {
|
|
109
|
+
type: 'problem',
|
|
110
|
+
docs: {
|
|
111
|
+
description: `react2angular: Always wrap react components in an error boundary`,
|
|
112
|
+
},
|
|
113
|
+
fixable: 'code',
|
|
114
|
+
},
|
|
115
|
+
create: rule,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
export default ruleModule;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import ruleTester from '../utils/ruleTester';
|
|
2
|
+
import rule from './rest-prefer-static-strings-in-initializer';
|
|
3
|
+
|
|
4
|
+
ruleTester.run('rest-prefer-static-strings-in-initializer', rule, {
|
|
5
|
+
valid: [
|
|
6
|
+
{ code: "REST('/foo/bar').path(id).get()" },
|
|
7
|
+
{ code: "REST('/foo/bar').path().get()" },
|
|
8
|
+
{ code: "REST(id).path('foo').get()" },
|
|
9
|
+
],
|
|
10
|
+
invalid: [
|
|
11
|
+
{
|
|
12
|
+
code: "REST().path('foo').path('bar')",
|
|
13
|
+
output: "REST('/foo').path('bar')",
|
|
14
|
+
errors: ["Prefer REST('/foo/bar') over REST().path('foo', 'bar')"],
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
code: "REST('foo').path('bar').get()",
|
|
18
|
+
output: "REST('foo/bar').get()",
|
|
19
|
+
errors: ["Prefer REST('/foo/bar') over REST().path('foo', 'bar')"],
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
// Process one path arg at a time
|
|
23
|
+
code: "REST('foo').path('bar', 'baz').get()",
|
|
24
|
+
output: "REST('foo/bar').path('baz').get()",
|
|
25
|
+
errors: ["Prefer REST('/foo/bar') over REST().path('foo', 'bar')"],
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
// Process one path arg at a time
|
|
29
|
+
code: "REST('foo').path('bar', 'baz').get()",
|
|
30
|
+
output: "REST('foo/bar').path('baz').get()",
|
|
31
|
+
errors: ["Prefer REST('/foo/bar') over REST().path('foo', 'bar')"],
|
|
32
|
+
},
|
|
33
|
+
],
|
|
34
|
+
});
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Migrate REST().path('foo', 'bar').path('baz').get() to REST('/foo/bar/baz').get()
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import type { Rule } from 'eslint';
|
|
6
|
+
import type { CallExpression, Literal } from 'estree';
|
|
7
|
+
import * as _ from 'lodash/fp';
|
|
8
|
+
|
|
9
|
+
import { getCallChain, getCallingIdentifierName, isLiteral } from '../utils/utils';
|
|
10
|
+
const getCallName = _.get('callee.property.name');
|
|
11
|
+
|
|
12
|
+
const ruleModule: Rule.RuleModule = {
|
|
13
|
+
create(context) {
|
|
14
|
+
return {
|
|
15
|
+
/**
|
|
16
|
+
* Look for chains of CallExpressions that are part of a REST().path() call
|
|
17
|
+
*/
|
|
18
|
+
CallExpression(node: CallExpression & Rule.NodeParentExtension) {
|
|
19
|
+
const callingIdentifierName = getCallingIdentifierName(node);
|
|
20
|
+
if (node.parent.type === 'MemberExpression' || callingIdentifierName !== 'REST') {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// an array of CallExpressions, i.e. for API.one().all().get() -> [.one, .all, .get]
|
|
25
|
+
const callChain = getCallChain(node);
|
|
26
|
+
|
|
27
|
+
// Look for a REST().path().whatever() call
|
|
28
|
+
if (!callChain[1] || getCallName(callChain[1]) !== 'path') {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const restCall = callChain[0];
|
|
33
|
+
const pathCall = callChain[1];
|
|
34
|
+
|
|
35
|
+
const restArg = restCall.arguments[0] as Literal;
|
|
36
|
+
const firstPathArg = pathCall.arguments[0] as Literal;
|
|
37
|
+
|
|
38
|
+
// Only REST('literal').path('literal', ...)
|
|
39
|
+
// Ignores: REST(variable) and REST().path(variable)
|
|
40
|
+
if ((restArg && !isLiteral(restArg)) || !isLiteral(firstPathArg)) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const message = `Prefer REST('/foo/bar') over REST().path('foo', 'bar')`;
|
|
45
|
+
|
|
46
|
+
function fix(fixer) {
|
|
47
|
+
const fixes = [];
|
|
48
|
+
const restCallEnd = restCall.range[1];
|
|
49
|
+
if (restArg) {
|
|
50
|
+
// REST('/foo').path('bar')
|
|
51
|
+
// Join '/foo' and '/bar' and replace the rest arg
|
|
52
|
+
// REST('/foo/bar').path('bar');
|
|
53
|
+
fixes.push(fixer.replaceText(restArg, `'${restArg.value}/${firstPathArg.value}'`));
|
|
54
|
+
} else {
|
|
55
|
+
// REST().path('foo')
|
|
56
|
+
// Insert text between the parentheses
|
|
57
|
+
// REST('foo').path('foo');
|
|
58
|
+
fixes.push(fixer.insertTextAfterRange([restCallEnd - 1, restCallEnd - 1], `'/${firstPathArg.value}'`));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (pathCall.arguments.length === 1) {
|
|
62
|
+
// REST('foo').path('foo');
|
|
63
|
+
// Remove the entire .path() call
|
|
64
|
+
// REST('foo');
|
|
65
|
+
fixes.push(fixer.removeRange([restCallEnd, pathCall.range[1]]));
|
|
66
|
+
} else {
|
|
67
|
+
/** @type {Literal} */
|
|
68
|
+
const secondPathArg = pathCall.arguments[1];
|
|
69
|
+
// Remove the first .path() call argument
|
|
70
|
+
fixes.push(fixer.removeRange([firstPathArg.range[0], secondPathArg.range[0]]));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return fixes;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
context.report({ node, message, fix });
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
},
|
|
80
|
+
meta: {
|
|
81
|
+
fixable: 'code',
|
|
82
|
+
type: 'problem',
|
|
83
|
+
docs: {
|
|
84
|
+
description: 'Migrate from API.xyz() to REST(path)',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
export default ruleModule;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import ruleTester from '../utils/ruleTester';
|
|
2
|
+
import rule from './RULENAME';
|
|
3
|
+
|
|
4
|
+
ruleTester.run('RULENAME', rule, {
|
|
5
|
+
valid: [
|
|
6
|
+
{
|
|
7
|
+
code: "import { Something } from 'somewhere';",
|
|
8
|
+
},
|
|
9
|
+
],
|
|
10
|
+
invalid: [
|
|
11
|
+
{
|
|
12
|
+
code: "import { API } from '@spinnaker/core';",
|
|
13
|
+
output: "import { } from '@spinnaker/core';",
|
|
14
|
+
errors: ['Do not import API'],
|
|
15
|
+
},
|
|
16
|
+
],
|
|
17
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Rule } from 'eslint';
|
|
2
|
+
|
|
3
|
+
const rule: Rule.RuleModule = {
|
|
4
|
+
create: (context) => ({
|
|
5
|
+
ImportSpecifier(node) {
|
|
6
|
+
if (node.local.name === 'API') {
|
|
7
|
+
const message = 'Do not import API';
|
|
8
|
+
context.report({ node, message, fix: (fixer) => fixer.remove(node) });
|
|
9
|
+
}
|
|
10
|
+
},
|
|
11
|
+
}),
|
|
12
|
+
meta: {
|
|
13
|
+
fixable: 'code',
|
|
14
|
+
type: 'problem',
|
|
15
|
+
docs: {
|
|
16
|
+
description: 'Do not import API',
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export default rule;
|
package/test.eslintrc
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
parser: '@typescript-eslint/parser',
|
|
3
|
+
"rules": {
|
|
4
|
+
},
|
|
5
|
+
"parserOptions": {
|
|
6
|
+
"ecmaVersion": 8,
|
|
7
|
+
"sourceType": "module"
|
|
8
|
+
},
|
|
9
|
+
"env": {
|
|
10
|
+
"browser": true,
|
|
11
|
+
"node": true,
|
|
12
|
+
"es6": true,
|
|
13
|
+
"jasmine": true,
|
|
14
|
+
},
|
|
15
|
+
"globals": {
|
|
16
|
+
"angular": true,
|
|
17
|
+
"$": true,
|
|
18
|
+
"_": true,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/bin/sh
|
|
2
|
+
RULESDIR=`dirname $0`/rules;
|
|
3
|
+
RULESDIRTEMP=$RULESDIR/temp
|
|
4
|
+
if [[ -z "$1" ]] ; then
|
|
5
|
+
echo "please provide the rule to run against ../../app and ../../packages:\n";
|
|
6
|
+
ls $RULESDIR | grep -v '\.spec\.' | sed -e 's/\.js$//'
|
|
7
|
+
else
|
|
8
|
+
RULEFILE=$(basename $(ls $RULESDIR/$1.[tj]s));
|
|
9
|
+
RULECONFIG={\"$1\":2}
|
|
10
|
+
echo RULE=$RULE
|
|
11
|
+
echo RULECONFIG=$RULECONFIG
|
|
12
|
+
mkdir -p $RULESDIRTEMP
|
|
13
|
+
ln -s ../$RULEFILE $RULESDIRTEMP
|
|
14
|
+
shift;
|
|
15
|
+
echo "node ${NODE_OPTS} ../../node_modules/.bin/eslint --ext js,ts,jsx,tsx --no-eslintrc -c test.eslintrc --rulesdir $RULESDIRTEMP --rule $RULECONFIG ../../app ../../packages $*"
|
|
16
|
+
node ${NODE_OPTS} ../../node_modules/.bin/eslint --ext js,ts,jsx,tsx --no-eslintrc -c test.eslintrc --rulesdir $RULESDIRTEMP --rule $RULECONFIG ../../app ../../packages $*
|
|
17
|
+
rm -rf $RULESDIRTEMP
|
|
18
|
+
fi
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"allowJs": true,
|
|
4
|
+
"allowSyntheticDefaultImports": true,
|
|
5
|
+
"esModuleInterop": true,
|
|
6
|
+
"module": "CommonJS",
|
|
7
|
+
"noEmit": true,
|
|
8
|
+
"lib": ["ES2020"],
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"downlevelIteration": true,
|
|
11
|
+
"types": ["node", "jest"]
|
|
12
|
+
},
|
|
13
|
+
"include": ["rules/**/*.ts", "utils/**/*.ts"],
|
|
14
|
+
"ts-node": {
|
|
15
|
+
"transpileOnly": true
|
|
16
|
+
}
|
|
17
|
+
}
|