@spinnaker/eslint-plugin 0.0.0-main-2

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.
Files changed (61) hide show
  1. package/.eslintignore +3 -0
  2. package/CHANGELOG.md +96 -0
  3. package/LICENSE.txt +203 -0
  4. package/README.md +259 -0
  5. package/babel.config.js +3 -0
  6. package/base.config.js +86 -0
  7. package/create-rule.js +67 -0
  8. package/eslint-plugin.ts +47 -0
  9. package/index.js +6 -0
  10. package/newrule.sh +88 -0
  11. package/none.config.js +18 -0
  12. package/package.json +55 -0
  13. package/rules/api-deprecation.spec.ts +79 -0
  14. package/rules/api-deprecation.ts +254 -0
  15. package/rules/api-no-slashes.spec.ts +84 -0
  16. package/rules/api-no-slashes.ts +148 -0
  17. package/rules/api-no-unused-chaining.spec.ts +26 -0
  18. package/rules/api-no-unused-chaining.ts +47 -0
  19. package/rules/import-from-alias-not-npm.spec.ts +22 -0
  20. package/rules/import-from-alias-not-npm.ts +53 -0
  21. package/rules/import-from-npm-not-alias.spec.ts +24 -0
  22. package/rules/import-from-npm-not-alias.ts +56 -0
  23. package/rules/import-from-npm-not-relative.spec.ts +22 -0
  24. package/rules/import-from-npm-not-relative.ts +57 -0
  25. package/rules/import-from-presentation-not-core.spec.ts +44 -0
  26. package/rules/import-from-presentation-not-core.ts +106 -0
  27. package/rules/import-relative-within-subpackage.spec.ts +51 -0
  28. package/rules/import-relative-within-subpackage.ts +71 -0
  29. package/rules/import-sort.spec.ts +85 -0
  30. package/rules/import-sort.ts +280 -0
  31. package/rules/migrate-to-mock-http-client.spec.ts +78 -0
  32. package/rules/migrate-to-mock-http-client.ts +122 -0
  33. package/rules/ng-no-component-class.spec.ts +45 -0
  34. package/rules/ng-no-component-class.ts +68 -0
  35. package/rules/ng-no-module-export.spec.ts +26 -0
  36. package/rules/ng-no-module-export.ts +117 -0
  37. package/rules/ng-no-require-angularjs.spec.ts +27 -0
  38. package/rules/ng-no-require-angularjs.ts +94 -0
  39. package/rules/ng-no-require-module-deps.spec.ts +33 -0
  40. package/rules/ng-no-require-module-deps.ts +211 -0
  41. package/rules/ng-strictdi.spec.ts +100 -0
  42. package/rules/ng-strictdi.ts +304 -0
  43. package/rules/prefer-promise-like.spec.ts +75 -0
  44. package/rules/prefer-promise-like.ts +108 -0
  45. package/rules/react2angular-with-error-boundary.spec.ts +29 -0
  46. package/rules/react2angular-with-error-boundary.ts +118 -0
  47. package/rules/rest-prefer-static-strings-in-initializer.spec.ts +34 -0
  48. package/rules/rest-prefer-static-strings-in-initializer.ts +89 -0
  49. package/template/template-rule.spec.ts +17 -0
  50. package/template/template-rule.ts +21 -0
  51. package/test.eslintrc +20 -0
  52. package/test_rule_against_deck_source.sh +18 -0
  53. package/tsconfig.json +17 -0
  54. package/utils/angular-rule/angular-rule.js +302 -0
  55. package/utils/angular-rule/false-values.js +6 -0
  56. package/utils/angular-rule/utils.js +624 -0
  57. package/utils/import-aliases.mock.ts +17 -0
  58. package/utils/import-aliases.ts +91 -0
  59. package/utils/mockModule.js +15 -0
  60. package/utils/ruleTester.js +8 -0
  61. package/utils/utils.ts +90 -0
@@ -0,0 +1,84 @@
1
+ import rule from './api-no-slashes';
2
+ import ruleTester from '../utils/ruleTester';
3
+ const errorMessage =
4
+ `Do not include slashes in API.one() or API.all() calls because arguments to .one() and .all() get url encoded.` +
5
+ `Instead, of API.one('foo/bar'), split into multiple arguments: API.one('foo', 'bar').`;
6
+
7
+ ruleTester.run('api-no-slashes', rule, {
8
+ valid: [
9
+ {
10
+ code: `API.one('foo', 'bar');`,
11
+ },
12
+ {
13
+ code: `const uri = "foo/bar/baz"; API.one(...uri.split('/'));`,
14
+ },
15
+ ],
16
+
17
+ invalid: [
18
+ {
19
+ code: `API.one('foo/bad');`,
20
+ output: `API.one('foo', 'bad');`,
21
+ errors: [errorMessage],
22
+ },
23
+ // Fixes slashes in chained .one().all().one() calls
24
+ {
25
+ code: `API.one('ok').one('ok').all('ok').one('foo/bad');`,
26
+ output: `API.one('ok').one('ok').all('ok').one('foo', 'bad');`,
27
+ errors: [errorMessage],
28
+ },
29
+ // Fixes slashes in varargs arguments
30
+ {
31
+ code: `API.one('ok').one('ok', 'foo/bad');`,
32
+ output: `API.one('ok').one('ok', 'foo', 'bad');`,
33
+ errors: [errorMessage],
34
+ },
35
+ // Variables which are initialized to a string literal with a slash transform to split+spread
36
+ {
37
+ code: `let foo = "foo/bad"; API.one(foo);`,
38
+ output: `let foo = "foo/bad"; API.one(...foo.split('/'));`,
39
+ errors: [errorMessage],
40
+ },
41
+ // Expressions with literal slashes transform to split+spread
42
+ {
43
+ code: "API.one(`foo/${bad}`, 'bad');",
44
+ output: "API.one(...(`foo/${bad}`).split('/'), 'bad');",
45
+ errors: [errorMessage],
46
+ },
47
+ // Variables whose initializer contains a slash
48
+ {
49
+ code: 'let foo = `foo/${variable}`; API.one(foo);',
50
+ output: "let foo = `foo/${variable}`; API.one(...foo.split('/'));",
51
+ errors: [errorMessage],
52
+ },
53
+ // Variables whose initializer contains a slash (anywhere)
54
+ {
55
+ code: 'let foo = "foo/bad" + variable; API.one(foo);',
56
+ output: `let foo = "foo/bad" + variable; API.one(...foo.split('/'));`,
57
+ errors: [errorMessage],
58
+ },
59
+ // Variables whose initializer contains a slash (mix of everything, still can be detected)
60
+ {
61
+ code: 'let foo = variable + `foo/${expr}` + "/bad/" + variable; API.one(foo);',
62
+ output: 'let foo = variable + `foo/${expr}` + "/bad/" + variable; API.one(...foo.split(\'/\'));',
63
+ errors: [errorMessage],
64
+ },
65
+ // Multiple errors, mix of styles
66
+ {
67
+ code: `const foo = "foo/bad"; API.all('api').one(foo).one('ok', 'bar/baz');`,
68
+ output: `const foo = "foo/bad"; API.all('api').one(...foo.split('/')).one('ok', 'bar', 'baz');`,
69
+ errors: [errorMessage, errorMessage], // two errors
70
+ },
71
+ // Detectable but unfixable
72
+ {
73
+ code: 'API.one(`foo/${template}`);',
74
+ output: "API.one(...(`foo/${template}`).split('/'));",
75
+ errors: [errorMessage], // two errors
76
+ },
77
+ // Detectable but unfixable
78
+ {
79
+ code: 'API.one("foo/" + expressions);',
80
+ output: `API.one(...("foo/" + expressions).split('/'));`,
81
+ errors: [errorMessage], // two errors
82
+ },
83
+ ],
84
+ });
@@ -0,0 +1,148 @@
1
+ import type { Rule } from 'eslint';
2
+ import type { CallExpression, Identifier, Literal } from 'estree';
3
+ import { get } from 'lodash';
4
+
5
+ import { getCallingIdentifier, getVariableInScope, isMemberExpression } from '../utils/utils';
6
+
7
+ /**
8
+ * No slashes in string literals passed to API.one() / API.all()
9
+ *
10
+ * @version 0.1.0
11
+ * @category
12
+ */
13
+ const rule = function (context: Rule.RuleContext) {
14
+ return {
15
+ CallExpression: function (node: CallExpression) {
16
+ const callee = node.callee;
17
+ const args = node.arguments;
18
+
19
+ // .one() or .all()
20
+ const propertyName =
21
+ (isMemberExpression(callee) && callee.property && 'name' in callee.property && callee.property.name) || '';
22
+
23
+ if (propertyName !== 'one' && propertyName !== 'all') {
24
+ // console.log('not one or all');
25
+ return;
26
+ }
27
+
28
+ // API.all('ok').one('ok', 'foo/bad', 'ok')
29
+ // ^^^
30
+ if ((getCallingIdentifier(node) || {}).name !== 'API') {
31
+ // console.log(getCallingIdentifier(callee));
32
+ // console.log('calling identifier not API');
33
+ return;
34
+ }
35
+
36
+ // Get the source code (think .toString()) of the AST node and find a slash
37
+ // This isn't 100% accurate, but it's good enough.
38
+ function sourceCodeHasSlash(node) {
39
+ const text = node ? context.getSourceCode().getText(node) : '';
40
+ const splitOnSlash = /\.split\(['"`]\/['"]\)/;
41
+ return !!(text && text.includes('/') && !splitOnSlash.exec(text));
42
+ }
43
+
44
+ function isArgLiteralWithSlash(arg): arg is Literal {
45
+ return arg.type === 'Literal' && sourceCodeHasSlash(arg);
46
+ }
47
+
48
+ const isVariableInitializedWithSlash = (arg): arg is Identifier => {
49
+ if (arg.type !== 'Identifier') {
50
+ return false;
51
+ }
52
+
53
+ // Check if the arg is a variable
54
+ const variable = getVariableInScope(context, arg);
55
+ // Find the variable's initializer
56
+ const initializer = get(variable, 'defs[0].node.init', null);
57
+ // Check if that variable's initializer contains a slash
58
+ return sourceCodeHasSlash(initializer);
59
+ };
60
+
61
+ // Literal:
62
+ // .one(okArg, 'foo/' + barid)
63
+ const literalsWithSlashes = args.filter((arg) => isArgLiteralWithSlash(arg)) as Literal[];
64
+
65
+ // Initializer:
66
+ // var badArg = `foo/${barid}`;
67
+ // var badArg2 = 'foo/' + barid`;
68
+ // .one(badArg)
69
+ // .one(badArg2)
70
+ const varsWithSlashes = args.filter((arg) => isVariableInitializedWithSlash(arg)) as Identifier[];
71
+
72
+ // Expression:
73
+ // .one(`foo/${barid}`)
74
+ const expressionWithSlash = args.filter(
75
+ (arg) => !literalsWithSlashes.includes(arg as any) && sourceCodeHasSlash(arg),
76
+ );
77
+
78
+ if (literalsWithSlashes.length === 0 && varsWithSlashes.length === 0 && expressionWithSlash.length === 0) {
79
+ return;
80
+ }
81
+
82
+ const message =
83
+ `Do not include slashes in API.one() or API.all() calls because arguments to .one() and .all() get url encoded.` +
84
+ `Instead, of API.one('foo/bar'), split into multiple arguments: API.one('foo', 'bar').`;
85
+
86
+ const fix = (fixer) => {
87
+ // within:
88
+ // API.one('foo/bad')
89
+ // replaces:
90
+ // 'foo/bad'
91
+ // with:
92
+ // 'foo', 'bad'
93
+ const literalArgFixes = literalsWithSlashes.map((arg) => {
94
+ const varArgs = (arg.value as string)
95
+ .split('/')
96
+ .map((segment) => "'" + segment + "'")
97
+ .join(', ');
98
+ return fixer.replaceText(arg, varArgs);
99
+ });
100
+
101
+ // within:
102
+ // let myVar = 'foo/bad';
103
+ // API.one(myVar)
104
+ // replaces argument:
105
+ // myVar
106
+ // with:
107
+ // ...myVar.split('/')
108
+ // i.e.:
109
+ // API.one(...myVar.split('/'))
110
+ const variableArgFixes = varsWithSlashes.map((arg) => {
111
+ // Found a variable with an initializer containing a slash
112
+ // Change the argument to be a string-split + array-spread
113
+ const spread = `...${arg.name}.split('/')`;
114
+ return fixer.replaceText(arg, spread);
115
+ });
116
+
117
+ // within:
118
+ // let prefix = 'foo/bad';
119
+ // API.one(`${prefix}/path`)
120
+ // replaces with:
121
+ // `${prefix}/path`
122
+ // with:
123
+ // ...(`${prefix}/path`).split('/')
124
+ // i.e.:
125
+ // API.one(...(`${prefix}/path`).split('/'))
126
+ const expressionWithSlashFixes = expressionWithSlash.map((arg) => {
127
+ const text = context.getSourceCode().getText(arg);
128
+ const spread = `...(${text}).split('/')`;
129
+ return fixer.replaceText(arg, spread);
130
+ });
131
+
132
+ return literalArgFixes.concat(variableArgFixes).concat(expressionWithSlashFixes);
133
+ };
134
+
135
+ context.report({ fix, node, message });
136
+ },
137
+ };
138
+ };
139
+
140
+ const ruleModule: Rule.RuleModule = {
141
+ meta: {
142
+ type: 'problem',
143
+ fixable: 'code',
144
+ },
145
+ create: rule,
146
+ };
147
+
148
+ export default ruleModule;
@@ -0,0 +1,26 @@
1
+ import rule from '../rules/api-no-unused-chaining';
2
+ import ruleTester from '../utils/ruleTester';
3
+ const errorMessage = (text) => `Unused API.xyz() method chaining no longer works. Re-assign the result of: ${text}`;
4
+
5
+ ruleTester.run('api-no-slashes', rule, {
6
+ valid: [
7
+ { code: `const fooBar = API.one('foo', 'bar');` },
8
+ { code: `let fooBar = API.one('foo', 'bar'); fooBar = fooBar.useCache()` },
9
+ { code: `const promise = API.one('foo', 'bar').all('baz').get();` },
10
+ ],
11
+
12
+ invalid: [
13
+ {
14
+ code: `API.one('foo/bad');`,
15
+ errors: [errorMessage(`API.one('foo/bad');`)],
16
+ },
17
+ {
18
+ code: `var foo = API.one('foo/bad'); foo.useCache(true);`,
19
+ errors: [errorMessage(`foo.useCache(true);`)],
20
+ },
21
+ {
22
+ code: `var foo = API.one('foo/bad'); foo.withParams({});`,
23
+ errors: [errorMessage(`foo.withParams({});`)],
24
+ },
25
+ ],
26
+ });
@@ -0,0 +1,47 @@
1
+ import type { Rule } from 'eslint';
2
+ import * as _ from 'lodash/fp';
3
+
4
+ const isApiConfigCall = _.overSome([
5
+ _.matches({ property: { type: 'Identifier', name: 'one' } }),
6
+ _.matches({ property: { type: 'Identifier', name: 'all' } }),
7
+ _.matches({ property: { type: 'Identifier', name: 'useCache' } }),
8
+ _.matches({ property: { type: 'Identifier', name: 'withParams' } }),
9
+ _.matches({ property: { type: 'Identifier', name: 'data' } }),
10
+ ]);
11
+
12
+ const falsePostitives = _.overSome([
13
+ _.matches({
14
+ property: { type: 'Identifier', name: 'data' },
15
+ object: { type: 'Identifier', name: '$element' },
16
+ }),
17
+ ]);
18
+
19
+ const create = function (context) {
20
+ return {
21
+ ExpressionStatement(node) {
22
+ if (
23
+ node.expression.type === 'CallExpression' &&
24
+ isApiConfigCall(node.expression.callee) &&
25
+ !falsePostitives(node.expression.callee)
26
+ ) {
27
+ const text = context.getSourceCode().getText(node);
28
+ context.report({
29
+ node,
30
+ message: `Unused API.xyz() method chaining no longer works. Re-assign the result of: ${text}`,
31
+ });
32
+ }
33
+ },
34
+ };
35
+ };
36
+
37
+ const ruleModule: Rule.RuleModule = {
38
+ create,
39
+ meta: {
40
+ type: 'problem',
41
+ docs: {
42
+ description: 'Check for unused API.xyx() calls',
43
+ },
44
+ },
45
+ };
46
+
47
+ export default ruleModule;
@@ -0,0 +1,22 @@
1
+ import rule from './import-from-alias-not-npm';
2
+ import ruleTester from '../utils/ruleTester';
3
+
4
+ ruleTester.run('import-from-alias-not-npm', rule, {
5
+ valid: [
6
+ {
7
+ filename: '/root/spinnaker/deck/packages/amazon/package/amazon_source_file.ts',
8
+ code: `import { Anything } from '@spinnaker/core';`,
9
+ },
10
+ ],
11
+
12
+ invalid: [
13
+ {
14
+ filename: '/root/spinnaker/deck/packages/core/package/core_source_file.ts',
15
+ code: `import { Anything } from '@spinnaker/core';`,
16
+ output: `import { Anything } from 'core';`,
17
+ errors: [
18
+ 'Do not use @spinnaker/core to import from core from code inside core. Instead, use the core alias or a relative import',
19
+ ],
20
+ },
21
+ ],
22
+ });
@@ -0,0 +1,53 @@
1
+ import type { Rule } from 'eslint';
2
+ import type { ImportDeclaration } from 'estree';
3
+
4
+ import { getImportFromNpm, getSourceFileDetails } from '../utils/import-aliases';
5
+
6
+ /**
7
+ * A group of rules that enforce spinnaker ES6 import alias conventions.
8
+ *
9
+ * Source code in a package (i.e., `core`) should not import from `@spinnaker/core`
10
+ *
11
+ * @version 0.1.0
12
+ * @category conventions
13
+ */
14
+ const rule = function (context: Rule.RuleContext) {
15
+ const { ownPackage } = getSourceFileDetails(context.getFilename());
16
+ if (!ownPackage) {
17
+ return {};
18
+ }
19
+
20
+ return {
21
+ ImportDeclaration(node: ImportDeclaration) {
22
+ if (node.source.type !== 'Literal' || !node.source.value) {
23
+ return;
24
+ }
25
+ const importString = node.source.value as string;
26
+ const importFromNpm = getImportFromNpm(importString);
27
+ if (!importFromNpm || importFromNpm.pkg !== ownPackage) {
28
+ return;
29
+ }
30
+
31
+ const { pkg, importPathWithSlash } = importFromNpm;
32
+ const message =
33
+ `Do not use ${importString} to import from ${ownPackage} from code inside ${ownPackage}. ` +
34
+ ` Instead, use the ${pkg} alias or a relative import`;
35
+
36
+ const fix = (fixer) => fixer.replaceText(node.source, `'${pkg}${importPathWithSlash}'`);
37
+ context.report({ fix, node, message });
38
+ },
39
+ };
40
+ };
41
+
42
+ const ruleModule: Rule.RuleModule = {
43
+ meta: {
44
+ type: 'problem',
45
+ docs: {
46
+ description: `Enforces spinnaker ES6 import conventions for package aliases`,
47
+ },
48
+ fixable: 'code',
49
+ },
50
+ create: rule,
51
+ };
52
+
53
+ export default ruleModule;
@@ -0,0 +1,24 @@
1
+ /* eslint-disable @spinnaker/import-sort */
2
+ import '../utils/import-aliases.mock';
3
+ import rule from './import-from-npm-not-alias';
4
+ import ruleTester from '../utils/ruleTester';
5
+
6
+ ruleTester.run('import-from-npm-not-alias', rule, {
7
+ valid: [
8
+ {
9
+ filename: '/root/spinnaker/deck/packages/amazon/package/amazon_source_file.ts',
10
+ code: `import { Anything } from 'amazon/otherpackage';`,
11
+ },
12
+ ],
13
+
14
+ invalid: [
15
+ {
16
+ filename: '/root/spinnaker/deck/packages/amazon/package/amazon_source_file.ts',
17
+ code: `import { Anything } from 'core/otherpackage';`,
18
+ output: `import { Anything } from '@spinnaker/core';`,
19
+ errors: [
20
+ 'Do not use an alias to import from core from code inside amazon. Instead, use the npm package @spinnaker/core',
21
+ ],
22
+ },
23
+ ],
24
+ });
@@ -0,0 +1,56 @@
1
+ import type { Rule } from 'eslint';
2
+ import type { ImportDeclaration } from 'estree';
3
+
4
+ import { getAliasImport, getAllSpinnakerPackages, getSourceFileDetails } from '../utils/import-aliases';
5
+
6
+ /**
7
+ * A group of rules that enforce spinnaker ES6 import alias conventions.
8
+ *
9
+ * Source code in a package (i.e., `amazon`) should not import from a different package using an alias (i.e., `core/`)
10
+ * Instead, it should import from `@spinnaker/core`
11
+ *
12
+ * @version 0.1.0
13
+ * @category conventions
14
+ */
15
+ const rule = function (context: Rule.RuleContext) {
16
+ const sourceFile = context.getFilename();
17
+ const { modulesPath, ownPackage } = getSourceFileDetails(sourceFile);
18
+ if (!ownPackage) {
19
+ return {};
20
+ }
21
+ const allSpinnakerPackages = getAllSpinnakerPackages(modulesPath);
22
+
23
+ return {
24
+ ImportDeclaration: function (node: ImportDeclaration & Rule.NodeParentExtension) {
25
+ if (node.source.type !== 'Literal' || !node.source.value) {
26
+ return;
27
+ }
28
+
29
+ const importString = node.source.value as string;
30
+ const aliasImport = getAliasImport(allSpinnakerPackages, importString);
31
+
32
+ if (!aliasImport || aliasImport.pkg === ownPackage) {
33
+ return;
34
+ }
35
+ const { pkg } = aliasImport;
36
+ const message =
37
+ `Do not use an alias to import from ${pkg} from code inside ${ownPackage}.` +
38
+ ` Instead, use the npm package @spinnaker/${pkg}`;
39
+
40
+ const fix = (fixer) => fixer.replaceText(node.source, `'@spinnaker/${pkg}'`);
41
+ context.report({ fix, node, message });
42
+ },
43
+ };
44
+ };
45
+
46
+ const importAliasesRule: Rule.RuleModule = {
47
+ meta: {
48
+ type: 'problem',
49
+ docs: {
50
+ description: `Enforces spinnaker ES6 import conventions for package aliases`,
51
+ },
52
+ fixable: 'code',
53
+ },
54
+ create: rule,
55
+ };
56
+ export default importAliasesRule;
@@ -0,0 +1,22 @@
1
+ import rule from './import-from-npm-not-relative';
2
+ import ruleTester from '../utils/ruleTester';
3
+
4
+ ruleTester.run('import-from-npm-not-relative', rule, {
5
+ valid: [
6
+ {
7
+ filename: '/root/spinnaker/deck/packages/amazon/package/amazon_source_file.ts',
8
+ code: `import { Anything } from '../othersubpackage/file2';`,
9
+ },
10
+ ],
11
+
12
+ invalid: [
13
+ {
14
+ filename: '/root/spinnaker/deck/packages/amazon/package/amazon_source_file.ts',
15
+ code: `import { Anything } from '../../core/subpackage/file2';`,
16
+ output: `import { Anything } from '@spinnaker/core';`,
17
+ errors: [
18
+ 'Do not use a relative import to import from core from code inside amazon. Instead, use the npm package @spinnaker/core',
19
+ ],
20
+ },
21
+ ],
22
+ });
@@ -0,0 +1,57 @@
1
+ import type { Rule } from 'eslint';
2
+ import type { ImportDeclaration } from 'estree';
3
+
4
+ import { getRelativeImport, getSourceFileDetails } from '../utils/import-aliases';
5
+
6
+ /**
7
+ * A group of rules that enforce spinnaker ES6 import alias conventions.
8
+ *
9
+ * Source code in a package (i.e., `amazon`) should not import from package (i.e., `core`) using a relative path.
10
+ * Instead, it should import from `@spinnaker/core`
11
+ *
12
+ * @version 0.1.0
13
+ * @category conventions
14
+ */
15
+ const rule = function (context: Rule.RuleContext) {
16
+ const sourceFile = context.getFilename();
17
+ const { modulesPath, sourceDirectory, ownPackage } = getSourceFileDetails(sourceFile);
18
+ if (!ownPackage) {
19
+ return {};
20
+ }
21
+
22
+ return {
23
+ ImportDeclaration: function (node: ImportDeclaration) {
24
+ if (node.source.type !== 'Literal' || !node.source.value) {
25
+ return;
26
+ }
27
+
28
+ const importString = node.source.value as string;
29
+ const relativeImport = getRelativeImport(sourceDirectory, modulesPath, importString);
30
+
31
+ if (!relativeImport || relativeImport.pkg === ownPackage) {
32
+ return;
33
+ }
34
+
35
+ const { pkg } = relativeImport;
36
+ const message =
37
+ `Do not use a relative import to import from ${pkg} from code inside ${ownPackage}.` +
38
+ ` Instead, use the npm package @spinnaker/${pkg}`;
39
+
40
+ const fix = (fixer) => fixer.replaceText(node.source, `'@spinnaker/${pkg}'`);
41
+ context.report({ fix, node, message });
42
+ },
43
+ };
44
+ };
45
+
46
+ const ruleModule: Rule.RuleModule = {
47
+ meta: {
48
+ type: 'problem',
49
+ docs: {
50
+ description: `Enforces spinnaker ES6 import conventions for package aliases`,
51
+ },
52
+ fixable: 'code',
53
+ },
54
+ create: rule,
55
+ };
56
+
57
+ export default ruleModule;
@@ -0,0 +1,44 @@
1
+ import rule from './import-from-presentation-not-core';
2
+ import ruleTester from '../utils/ruleTester';
3
+ const errorMessage = (moduleName) => `${moduleName} must be imported from @spinnaker/presentation`;
4
+
5
+ ruleTester.run('import-from-presentation-not-core', rule, {
6
+ valid: [
7
+ { code: `import { Icon, Illustration } from '@spinnaker/presentation';` },
8
+ { code: `import { LabeledValueList } from '@spinnaker/core';` },
9
+ { code: `import { Application } from '@spinnaker/core';` },
10
+ ],
11
+ invalid: [
12
+ {
13
+ code: `
14
+ import { Icon, NotMigratedModule } from '@spinnaker/core';
15
+ import { Foo } from '@spinnaker/presentation';
16
+ `,
17
+ errors: [errorMessage('Icon')],
18
+ output: `
19
+ import { NotMigratedModule } from '@spinnaker/core';
20
+ import { Foo, Icon } from '@spinnaker/presentation';
21
+ `,
22
+ },
23
+ {
24
+ code: `
25
+ import { Icon, NotMigratedModule } from '@spinnaker/core';
26
+ `,
27
+ errors: [errorMessage('Icon')],
28
+ output: `
29
+ import { NotMigratedModule } from '@spinnaker/core';\nimport {Icon} from '@spinnaker/presentation';
30
+ `,
31
+ },
32
+ {
33
+ code: `
34
+ import React from 'react';
35
+ import { Icon } from '@spinnaker/core';
36
+ `,
37
+ errors: [errorMessage('Icon')],
38
+ output: `
39
+ import React from 'react';
40
+ \nimport {Icon} from '@spinnaker/presentation';
41
+ `,
42
+ },
43
+ ],
44
+ });