@spinnaker/eslint-plugin 2026.2.2 → 2026.3.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/base.config.js +0 -9
- package/eslint-plugin.spec.ts +34 -0
- package/eslint-plugin.ts +1 -16
- package/legacy.config.js +72 -0
- package/none.config.js +0 -1
- package/package.json +5 -5
- package/rules/api-no-unused-chaining.spec.ts +5 -1
- package/rules/api-no-unused-chaining.ts +1 -12
- package/rules/import-sort.spec.ts +5 -5
- package/rules/import-sort.ts +2 -2
- package/test.eslintrc +12 -14
- package/utils/utils.spec.ts +16 -0
- package/utils/utils.ts +3 -1
- package/rules/migrate-to-mock-http-client.spec.ts +0 -78
- package/rules/migrate-to-mock-http-client.ts +0 -121
- package/rules/ng-no-component-class.spec.ts +0 -45
- package/rules/ng-no-component-class.ts +0 -67
- package/rules/ng-no-module-export.spec.ts +0 -26
- package/rules/ng-no-module-export.ts +0 -117
- package/rules/ng-no-require-angularjs.spec.ts +0 -27
- package/rules/ng-no-require-angularjs.ts +0 -92
- package/rules/ng-no-require-module-deps.spec.ts +0 -33
- package/rules/ng-no-require-module-deps.ts +0 -211
- package/rules/ng-strictdi.spec.ts +0 -100
- package/rules/ng-strictdi.ts +0 -304
- package/rules/prefer-promise-like.spec.ts +0 -75
- package/rules/prefer-promise-like.ts +0 -108
- package/rules/react2angular-with-error-boundary.spec.ts +0 -29
- package/rules/react2angular-with-error-boundary.ts +0 -119
- package/utils/angular-rule/angular-rule.js +0 -302
- package/utils/angular-rule/false-values.js +0 -6
- package/utils/angular-rule/utils.js +0 -625
package/base.config.js
CHANGED
|
@@ -32,7 +32,6 @@ module.exports = [
|
|
|
32
32
|
...globals.browser,
|
|
33
33
|
...globals.node,
|
|
34
34
|
...globals.jasmine,
|
|
35
|
-
angular: true,
|
|
36
35
|
$: true,
|
|
37
36
|
_: true,
|
|
38
37
|
},
|
|
@@ -54,14 +53,6 @@ module.exports = [
|
|
|
54
53
|
'@spinnaker/import-from-npm-not-relative': 2,
|
|
55
54
|
'@spinnaker/import-from-presentation-not-core': 2,
|
|
56
55
|
'@spinnaker/import-relative-within-subpackage': 2,
|
|
57
|
-
'@spinnaker/migrate-to-mock-http-client': 2,
|
|
58
|
-
'@spinnaker/ng-no-component-class': 2,
|
|
59
|
-
'@spinnaker/ng-no-module-export': 2,
|
|
60
|
-
'@spinnaker/ng-no-require-angularjs': 2,
|
|
61
|
-
'@spinnaker/ng-no-require-module-deps': 2,
|
|
62
|
-
'@spinnaker/ng-strictdi': 'off',
|
|
63
|
-
'@spinnaker/prefer-promise-like': 1,
|
|
64
|
-
'@spinnaker/react2angular-with-error-boundary': 2,
|
|
65
56
|
'@spinnaker/rest-prefer-static-strings-in-initializer': 2,
|
|
66
57
|
indent: 'off',
|
|
67
58
|
'member-ordering': 'off',
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import plugin from './eslint-plugin';
|
|
2
|
+
|
|
3
|
+
describe('ESLint config contracts', () => {
|
|
4
|
+
test('provides flat and legacy base configs', () => {
|
|
5
|
+
const configs = plugin.configs as Record<string, unknown>;
|
|
6
|
+
|
|
7
|
+
expect(Array.isArray(configs.base)).toBe(true);
|
|
8
|
+
expect(Array.isArray(configs['legacy-base'])).toBe(false);
|
|
9
|
+
expect(configs['legacy-base']).toEqual(
|
|
10
|
+
expect.objectContaining({
|
|
11
|
+
plugins: expect.arrayContaining(['@spinnaker/eslint-plugin']),
|
|
12
|
+
rules: expect.objectContaining({
|
|
13
|
+
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
|
|
14
|
+
}),
|
|
15
|
+
}),
|
|
16
|
+
);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('omits the retired migration rule while preserving the generic API chaining rule', () => {
|
|
20
|
+
const configs = plugin.configs as Record<string, unknown>;
|
|
21
|
+
const flatRules = (configs.base as Array<{ rules?: Record<string, unknown> }>)
|
|
22
|
+
.map((config) => config.rules)
|
|
23
|
+
.filter((rules): rules is Record<string, unknown> => Boolean(rules));
|
|
24
|
+
const legacyRules = (configs['legacy-base'] as { rules: Record<string, unknown> }).rules;
|
|
25
|
+
|
|
26
|
+
expect(plugin.rules).not.toHaveProperty('migrate-to-mock-http-client');
|
|
27
|
+
expect(flatRules.some((rules) => '@spinnaker/migrate-to-mock-http-client' in rules)).toBe(false);
|
|
28
|
+
expect(legacyRules).not.toHaveProperty('@spinnaker/migrate-to-mock-http-client');
|
|
29
|
+
|
|
30
|
+
expect(plugin.rules).toHaveProperty('api-no-unused-chaining');
|
|
31
|
+
expect(flatRules.some((rules) => '@spinnaker/api-no-unused-chaining' in rules)).toBe(true);
|
|
32
|
+
expect(legacyRules).toHaveProperty('@spinnaker/api-no-unused-chaining', 2);
|
|
33
|
+
});
|
|
34
|
+
});
|
package/eslint-plugin.ts
CHANGED
|
@@ -7,14 +7,6 @@ import importFromNpmNotRelative from './rules/import-from-npm-not-relative';
|
|
|
7
7
|
import importFromPresentationNotCore from './rules/import-from-presentation-not-core';
|
|
8
8
|
import importRelativeWithinSubpackage from './rules/import-relative-within-subpackage';
|
|
9
9
|
import importSort from './rules/import-sort';
|
|
10
|
-
import migrateToMockHttpClient from './rules/migrate-to-mock-http-client';
|
|
11
|
-
import ngNoComponentClass from './rules/ng-no-component-class';
|
|
12
|
-
import ngNoModuleExport from './rules/ng-no-module-export';
|
|
13
|
-
import ngNoRequireAngularJS from './rules/ng-no-require-angularjs';
|
|
14
|
-
import ngNoRequireModuleDeps from './rules/ng-no-require-module-deps';
|
|
15
|
-
import ngStrictDI from './rules/ng-strictdi';
|
|
16
|
-
import preferPromiseLike from './rules/prefer-promise-like';
|
|
17
|
-
import react2angularWithErrorBoundary from './rules/react2angular-with-error-boundary';
|
|
18
10
|
import restPreferStaticStringsInInitializer from './rules/rest-prefer-static-strings-in-initializer';
|
|
19
11
|
|
|
20
12
|
const rules = {
|
|
@@ -27,14 +19,6 @@ const rules = {
|
|
|
27
19
|
'import-from-presentation-not-core': importFromPresentationNotCore,
|
|
28
20
|
'import-relative-within-subpackage': importRelativeWithinSubpackage,
|
|
29
21
|
'import-sort': importSort,
|
|
30
|
-
'migrate-to-mock-http-client': migrateToMockHttpClient,
|
|
31
|
-
'ng-no-component-class': ngNoComponentClass,
|
|
32
|
-
'ng-no-module-export': ngNoModuleExport,
|
|
33
|
-
'ng-no-require-angularjs': ngNoRequireAngularJS,
|
|
34
|
-
'ng-no-require-module-deps': ngNoRequireModuleDeps,
|
|
35
|
-
'ng-strictdi': ngStrictDI,
|
|
36
|
-
'prefer-promise-like': preferPromiseLike,
|
|
37
|
-
'react2angular-with-error-boundary': react2angularWithErrorBoundary,
|
|
38
22
|
'rest-prefer-static-strings-in-initializer': restPreferStaticStringsInInitializer,
|
|
39
23
|
};
|
|
40
24
|
|
|
@@ -44,6 +28,7 @@ const plugin = {
|
|
|
44
28
|
get configs() {
|
|
45
29
|
return {
|
|
46
30
|
base: require('./base.config.js'),
|
|
31
|
+
'legacy-base': require('./legacy.config.js'),
|
|
47
32
|
none: require('./none.config.js'),
|
|
48
33
|
};
|
|
49
34
|
},
|
package/legacy.config.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
env: {
|
|
3
|
+
browser: true,
|
|
4
|
+
jasmine: true,
|
|
5
|
+
node: true,
|
|
6
|
+
},
|
|
7
|
+
extends: ['eslint:recommended', 'prettier', 'plugin:@typescript-eslint/recommended'],
|
|
8
|
+
globals: {
|
|
9
|
+
$: 'readonly',
|
|
10
|
+
_: 'readonly',
|
|
11
|
+
},
|
|
12
|
+
ignorePatterns: ['**/*.spec.*', 'template/**/*'],
|
|
13
|
+
parser: '@typescript-eslint/parser',
|
|
14
|
+
parserOptions: {
|
|
15
|
+
sourceType: 'module',
|
|
16
|
+
},
|
|
17
|
+
plugins: ['@spinnaker/eslint-plugin', 'react-hooks'],
|
|
18
|
+
rules: {
|
|
19
|
+
'@spinnaker/api-deprecation': 2,
|
|
20
|
+
'@spinnaker/api-no-slashes': 2,
|
|
21
|
+
'@spinnaker/api-no-unused-chaining': 2,
|
|
22
|
+
'@spinnaker/import-from-alias-not-npm': 2,
|
|
23
|
+
'@spinnaker/import-from-npm-not-alias': 2,
|
|
24
|
+
'@spinnaker/import-from-npm-not-relative': 2,
|
|
25
|
+
'@spinnaker/import-from-presentation-not-core': 2,
|
|
26
|
+
'@spinnaker/import-relative-within-subpackage': 2,
|
|
27
|
+
'@spinnaker/import-sort': 1,
|
|
28
|
+
'@spinnaker/rest-prefer-static-strings-in-initializer': 2,
|
|
29
|
+
'@typescript-eslint/array-type': ['error', { default: 'array-simple' }],
|
|
30
|
+
'@typescript-eslint/ban-ts-comment': 'off',
|
|
31
|
+
'@typescript-eslint/ban-types': 'off',
|
|
32
|
+
'@typescript-eslint/consistent-type-imports': ['error', { prefer: 'type-imports' }],
|
|
33
|
+
'@typescript-eslint/explicit-function-return-type': 'off',
|
|
34
|
+
'@typescript-eslint/explicit-member-accessibility': 'off',
|
|
35
|
+
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
|
36
|
+
'@typescript-eslint/indent': 'off',
|
|
37
|
+
'@typescript-eslint/no-empty-function': 'off',
|
|
38
|
+
'@typescript-eslint/no-empty-interface': 'off',
|
|
39
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
40
|
+
'@typescript-eslint/no-parameter-properties': 'off',
|
|
41
|
+
'@typescript-eslint/no-require-imports': 'off',
|
|
42
|
+
'@typescript-eslint/no-this-alias': 'off',
|
|
43
|
+
'@typescript-eslint/no-unused-expressions': 'off',
|
|
44
|
+
'@typescript-eslint/no-unused-vars': 'off',
|
|
45
|
+
'@typescript-eslint/no-use-before-define': 'off',
|
|
46
|
+
'@typescript-eslint/no-var-requires': 'off',
|
|
47
|
+
'@typescript-eslint/triple-slash-reference': 'off',
|
|
48
|
+
indent: 'off',
|
|
49
|
+
'no-console': ['error', { allow: ['warn', 'error'] }],
|
|
50
|
+
'no-extra-boolean-cast': 'off',
|
|
51
|
+
'no-prototype-builtins': 'off',
|
|
52
|
+
'one-var': ['error', { initialized: 'never' }],
|
|
53
|
+
'prefer-rest-params': 'off',
|
|
54
|
+
'prefer-spread': 'off',
|
|
55
|
+
'react-hooks/rules-of-hooks': 'error',
|
|
56
|
+
'require-atomic-updates': 'off',
|
|
57
|
+
},
|
|
58
|
+
overrides: [
|
|
59
|
+
{
|
|
60
|
+
files: ['**/*.js', '**/*.jsx'],
|
|
61
|
+
rules: {
|
|
62
|
+
'@typescript-eslint/no-use-before-define': 'off',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
files: ['**/*.ts', '**/*.tsx'],
|
|
67
|
+
rules: {
|
|
68
|
+
'no-undef': 'off',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
],
|
|
72
|
+
};
|
package/none.config.js
CHANGED
package/package.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"type": "git",
|
|
5
5
|
"url": "https://github.com/spinnaker/spinnaker.git"
|
|
6
6
|
},
|
|
7
|
-
"version": "2026.
|
|
7
|
+
"version": "2026.3.0",
|
|
8
8
|
"main": "index.js",
|
|
9
9
|
"license": "Apache-2.0",
|
|
10
10
|
"publishConfig": {
|
|
@@ -22,9 +22,10 @@
|
|
|
22
22
|
"ts-node": "*"
|
|
23
23
|
},
|
|
24
24
|
"devDependencies": {
|
|
25
|
+
"@babel/preset-env": "^7.15.0",
|
|
25
26
|
"@babel/preset-typescript": "^7.15.0",
|
|
26
27
|
"@eslint/compat": "^2.0.3",
|
|
27
|
-
"@eslint/eslintrc": "^3.3.
|
|
28
|
+
"@eslint/eslintrc": "^3.3.6",
|
|
28
29
|
"@eslint/js": "^9.39.2",
|
|
29
30
|
"@types/eslint": "9.6.1",
|
|
30
31
|
"@types/estree": "*",
|
|
@@ -57,10 +58,9 @@
|
|
|
57
58
|
],
|
|
58
59
|
"jest": {
|
|
59
60
|
"testPathIgnorePatterns": [
|
|
60
|
-
"<rootDir>/template"
|
|
61
|
-
"<rootDir>/rules/ng-strictdi.spec.ts"
|
|
61
|
+
"<rootDir>/template"
|
|
62
62
|
],
|
|
63
63
|
"testEnvironment": "node"
|
|
64
64
|
},
|
|
65
|
-
"gitHead": "
|
|
65
|
+
"gitHead": "0f7d75f5d4b42c1675e2ecc69a53f8eb73090eb9"
|
|
66
66
|
}
|
|
@@ -2,7 +2,7 @@ import rule from '../rules/api-no-unused-chaining';
|
|
|
2
2
|
import ruleTester from '../utils/ruleTester';
|
|
3
3
|
const errorMessage = (text) => `Unused API.xyz() method chaining no longer works. Re-assign the result of: ${text}`;
|
|
4
4
|
|
|
5
|
-
ruleTester.run('api-no-
|
|
5
|
+
ruleTester.run('api-no-unused-chaining', rule, {
|
|
6
6
|
valid: [
|
|
7
7
|
{ code: `const fooBar = API.one('foo', 'bar');` },
|
|
8
8
|
{ code: `let fooBar = API.one('foo', 'bar'); fooBar = fooBar.useCache()` },
|
|
@@ -22,5 +22,9 @@ ruleTester.run('api-no-slashes', rule, {
|
|
|
22
22
|
code: `var foo = API.one('foo/bad'); foo.withParams({});`,
|
|
23
23
|
errors: [errorMessage(`foo.withParams({});`)],
|
|
24
24
|
},
|
|
25
|
+
{
|
|
26
|
+
code: `var foo = API.one('foo/bad'); foo.data({});`,
|
|
27
|
+
errors: [errorMessage(`foo.data({});`)],
|
|
28
|
+
},
|
|
25
29
|
],
|
|
26
30
|
});
|
|
@@ -9,21 +9,10 @@ const isApiConfigCall = _.overSome([
|
|
|
9
9
|
_.matches({ property: { type: 'Identifier', name: 'data' } }),
|
|
10
10
|
]);
|
|
11
11
|
|
|
12
|
-
const falsePostitives = _.overSome([
|
|
13
|
-
_.matches({
|
|
14
|
-
property: { type: 'Identifier', name: 'data' },
|
|
15
|
-
object: { type: 'Identifier', name: '$element' },
|
|
16
|
-
}),
|
|
17
|
-
]);
|
|
18
|
-
|
|
19
12
|
const create = function (context) {
|
|
20
13
|
return {
|
|
21
14
|
ExpressionStatement(node) {
|
|
22
|
-
if (
|
|
23
|
-
node.expression.type === 'CallExpression' &&
|
|
24
|
-
isApiConfigCall(node.expression.callee) &&
|
|
25
|
-
!falsePostitives(node.expression.callee)
|
|
26
|
-
) {
|
|
15
|
+
if (node.expression.type === 'CallExpression' && isApiConfigCall(node.expression.callee)) {
|
|
27
16
|
const text = context.getSourceCode().getText(node);
|
|
28
17
|
context.report({
|
|
29
18
|
node,
|
|
@@ -5,7 +5,7 @@ ruleTester.run('import-sort', rule, {
|
|
|
5
5
|
valid: [
|
|
6
6
|
{
|
|
7
7
|
code: `
|
|
8
|
-
import
|
|
8
|
+
import axios from 'axios';
|
|
9
9
|
import 'jquery';
|
|
10
10
|
import React, { useCallback, useState } from 'react';
|
|
11
11
|
import * as Select from 'react-select';
|
|
@@ -29,11 +29,11 @@ import './styles.less';
|
|
|
29
29
|
{
|
|
30
30
|
code: `
|
|
31
31
|
import React, {useState, useCallback} from 'react';
|
|
32
|
-
import
|
|
32
|
+
import axios from 'axios';
|
|
33
33
|
import * as Select from 'react-select';
|
|
34
34
|
`,
|
|
35
35
|
output: `
|
|
36
|
-
import
|
|
36
|
+
import axios from 'axios';
|
|
37
37
|
import React, { useCallback, useState } from 'react';
|
|
38
38
|
import * as Select from 'react-select';
|
|
39
39
|
`,
|
|
@@ -47,7 +47,7 @@ const {useState, useCallback} = React;
|
|
|
47
47
|
|
|
48
48
|
import { Application } from 'core/application';
|
|
49
49
|
import Bar from "./bar";
|
|
50
|
-
import
|
|
50
|
+
import axios from 'axios';
|
|
51
51
|
// Some comment about react-select
|
|
52
52
|
import * as Select from 'react-select';
|
|
53
53
|
import {
|
|
@@ -62,7 +62,7 @@ import 'bootstrap.less';
|
|
|
62
62
|
`,
|
|
63
63
|
// For some strange reason eslint fixer writes additional newline characters
|
|
64
64
|
output: `
|
|
65
|
-
import
|
|
65
|
+
import axios from 'axios';
|
|
66
66
|
import 'jquery';
|
|
67
67
|
import React from 'react';
|
|
68
68
|
// Some comment about react-select
|
package/rules/import-sort.ts
CHANGED
|
@@ -142,9 +142,9 @@ const printImportDeclaration = (context, importDeclaration) => {
|
|
|
142
142
|
* Returns a custom textual representation of import declarations which will be used to verify if they are already
|
|
143
143
|
* sorted. For example
|
|
144
144
|
*
|
|
145
|
-
* `import React, {useState, useCallback} from 'react';\nimport
|
|
145
|
+
* `import React, {useState, useCallback} from 'react';\nimport axios from 'axios';`
|
|
146
146
|
* will be written as
|
|
147
|
-
* `react: React, useState, useCallback\
|
|
147
|
+
* `react: React, useState, useCallback\naxios: axios`
|
|
148
148
|
*/
|
|
149
149
|
const getText = (importDeclarations) => {
|
|
150
150
|
return importDeclarations.reduce((output, importDeclaration) => {
|
package/test.eslintrc
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
parser: '@typescript-eslint/parser',
|
|
3
|
-
|
|
3
|
+
rules: {},
|
|
4
|
+
parserOptions: {
|
|
5
|
+
ecmaVersion: 8,
|
|
6
|
+
sourceType: 'module',
|
|
4
7
|
},
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
+
env: {
|
|
9
|
+
browser: true,
|
|
10
|
+
node: true,
|
|
11
|
+
es6: true,
|
|
12
|
+
jasmine: true,
|
|
8
13
|
},
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
"es6": true,
|
|
13
|
-
"jasmine": true,
|
|
14
|
+
globals: {
|
|
15
|
+
$: true,
|
|
16
|
+
_: true,
|
|
14
17
|
},
|
|
15
|
-
"globals": {
|
|
16
|
-
"angular": true,
|
|
17
|
-
"$": true,
|
|
18
|
-
"_": true,
|
|
19
|
-
}
|
|
20
18
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { Rule, Scope } from 'eslint';
|
|
2
|
+
|
|
3
|
+
import { getVariableInScope } from './utils';
|
|
4
|
+
|
|
5
|
+
describe('getVariableInScope', () => {
|
|
6
|
+
test('uses the legacy context scope API when SourceCode does not provide one', () => {
|
|
7
|
+
const variable = {} as Scope.Variable;
|
|
8
|
+
const context = ({
|
|
9
|
+
getScope: () => ({
|
|
10
|
+
references: [{ identifier: { name: 'value' }, resolved: variable }],
|
|
11
|
+
}),
|
|
12
|
+
} as unknown) as Rule.RuleContext;
|
|
13
|
+
|
|
14
|
+
expect(getVariableInScope(context, { name: 'value', type: 'Identifier' })).toBe(variable);
|
|
15
|
+
});
|
|
16
|
+
});
|
package/utils/utils.ts
CHANGED
|
@@ -53,7 +53,9 @@ export function getVariableInScope(context: Rule.RuleContext, identifier: Identi
|
|
|
53
53
|
return undefined;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
-
const { references } = context.sourceCode
|
|
56
|
+
const { references } = context.sourceCode?.getScope
|
|
57
|
+
? context.sourceCode.getScope((identifier as unknown) as Rule.Node)
|
|
58
|
+
: ((context as unknown) as { getScope: () => Pick<Scope.Scope, 'references'> }).getScope();
|
|
57
59
|
const ref = references.find((r) => r.identifier.name === identifier.name);
|
|
58
60
|
return ref ? ref.resolved : undefined;
|
|
59
61
|
}
|
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import rule from './migrate-to-mock-http-client';
|
|
2
|
-
import ruleTester from '../utils/ruleTester';
|
|
3
|
-
|
|
4
|
-
ruleTester.run('migrate-to-mock-http-client', rule, {
|
|
5
|
-
valid: [
|
|
6
|
-
{
|
|
7
|
-
code: 'it(() => { const http = mockHttpClient(); })',
|
|
8
|
-
},
|
|
9
|
-
],
|
|
10
|
-
invalid: [
|
|
11
|
-
{
|
|
12
|
-
code: `it('does things', () => { $httpBackend.flush() })`,
|
|
13
|
-
output: `it('does things', async () => { $httpBackend.flush() })`,
|
|
14
|
-
errors: ['Migrate to MockHttpClient (step 1): make test function async'],
|
|
15
|
-
},
|
|
16
|
-
|
|
17
|
-
// Step 1 make async
|
|
18
|
-
{
|
|
19
|
-
code: `
|
|
20
|
-
describe('foo bar', () => {
|
|
21
|
-
it('does things', () => {
|
|
22
|
-
$httpBackend.flush()
|
|
23
|
-
})
|
|
24
|
-
})`,
|
|
25
|
-
output: `
|
|
26
|
-
describe('foo bar', () => {
|
|
27
|
-
it('does things', async () => {
|
|
28
|
-
$httpBackend.flush()
|
|
29
|
-
})
|
|
30
|
-
})`,
|
|
31
|
-
errors: ['Migrate to MockHttpClient (step 1): make test function async'],
|
|
32
|
-
},
|
|
33
|
-
|
|
34
|
-
// Step 2 create mock
|
|
35
|
-
{
|
|
36
|
-
code: `
|
|
37
|
-
describe('foo bar', () => {
|
|
38
|
-
it('does things', async () => {
|
|
39
|
-
$httpBackend.flush()
|
|
40
|
-
})
|
|
41
|
-
})`,
|
|
42
|
-
output: `import { mockHttpClient } from 'core/api/mock/jasmine';
|
|
43
|
-
|
|
44
|
-
describe('foo bar', () => {
|
|
45
|
-
it('does things', async () => {
|
|
46
|
-
const http = mockHttpClient();
|
|
47
|
-
$httpBackend.flush()
|
|
48
|
-
})
|
|
49
|
-
})`,
|
|
50
|
-
errors: ['Migrate to MockHttpClient (step 2): Create a MockHttpClient named "http"'],
|
|
51
|
-
},
|
|
52
|
-
|
|
53
|
-
// Step 3 change variables
|
|
54
|
-
{
|
|
55
|
-
code: `
|
|
56
|
-
import { mockHttpClient } from 'core/api/mock/jasmine';
|
|
57
|
-
describe('foo bar', () => {
|
|
58
|
-
it('does things', async () => {
|
|
59
|
-
const http = mockHttpClient();
|
|
60
|
-
$httpBackend.expectGET('/foo/bar').respond(200, { bar: 15 });
|
|
61
|
-
service.fetchBars();
|
|
62
|
-
$httpBackend.flush()
|
|
63
|
-
})
|
|
64
|
-
})`,
|
|
65
|
-
output: `
|
|
66
|
-
import { mockHttpClient } from 'core/api/mock/jasmine';
|
|
67
|
-
describe('foo bar', () => {
|
|
68
|
-
it('does things', async () => {
|
|
69
|
-
const http = mockHttpClient();
|
|
70
|
-
http.expectGET('/foo/bar').respond(200, { bar: 15 });
|
|
71
|
-
service.fetchBars();
|
|
72
|
-
await http.flush()
|
|
73
|
-
})
|
|
74
|
-
})`,
|
|
75
|
-
errors: ['Migrate to MockHttpClient (step 3): replace $httpBackend with http'],
|
|
76
|
-
},
|
|
77
|
-
],
|
|
78
|
-
});
|
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import type { Rule } from 'eslint';
|
|
2
|
-
|
|
3
|
-
import { getImportName } from '../utils/ast';
|
|
4
|
-
import { getProgram } from '../utils/utils';
|
|
5
|
-
|
|
6
|
-
const ruleModule: Rule.RuleModule = {
|
|
7
|
-
create(context) {
|
|
8
|
-
const text = (node) => context.getSourceCode().getText(node);
|
|
9
|
-
|
|
10
|
-
return {
|
|
11
|
-
CallExpression(_node: any) {
|
|
12
|
-
const node = _node;
|
|
13
|
-
/** it(() => {}) */
|
|
14
|
-
const isItBlock = node.callee.type === 'Identifier' && node.callee.name === 'it';
|
|
15
|
-
|
|
16
|
-
if (isItBlock) {
|
|
17
|
-
const itBlockText = text(node);
|
|
18
|
-
const testFunction = node.arguments[1] as any;
|
|
19
|
-
|
|
20
|
-
const doesFunctionIncludeHttpBackend = !!testFunction && itBlockText.includes('$httpBackend');
|
|
21
|
-
|
|
22
|
-
if (doesFunctionIncludeHttpBackend) {
|
|
23
|
-
const isFirstArgAFunction = ['FunctionExpression', 'ArrowFunctionExpression'].includes(testFunction.type);
|
|
24
|
-
|
|
25
|
-
if (isFirstArgAFunction) {
|
|
26
|
-
// Fix 1: make the test 'async'
|
|
27
|
-
if (testFunction.async !== true) {
|
|
28
|
-
return context.report({
|
|
29
|
-
node,
|
|
30
|
-
message: 'Migrate to MockHttpClient (step 1): make test function async',
|
|
31
|
-
fix: (fixer) => fixer.insertTextBefore(testFunction, 'async '),
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// Fix 2: Add a 'http' variable
|
|
36
|
-
if (
|
|
37
|
-
testFunction.body.type === 'BlockStatement' &&
|
|
38
|
-
!text(testFunction.body.body[0]).includes('mockHttpClient')
|
|
39
|
-
) {
|
|
40
|
-
const program = getProgram(node);
|
|
41
|
-
const allImports = program.body.filter((item) => item.type === 'ImportDeclaration') as any[];
|
|
42
|
-
|
|
43
|
-
const importSpecifiers = allImports
|
|
44
|
-
.map((decl) => decl.specifiers as any[])
|
|
45
|
-
.reduce((acc, x) => acc.concat(x), []);
|
|
46
|
-
|
|
47
|
-
const mockHttpClientImport = importSpecifiers.find((specifier) => {
|
|
48
|
-
return specifier.imported && getImportName(specifier.imported) === 'mockHttpClient';
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
return context.report({
|
|
52
|
-
node,
|
|
53
|
-
message: 'Migrate to MockHttpClient (step 2): Create a MockHttpClient named "http"',
|
|
54
|
-
fix: (fixer) => {
|
|
55
|
-
const insertHttp = fixer.insertTextBefore(
|
|
56
|
-
testFunction.body.body[0],
|
|
57
|
-
'const http = mockHttpClient();\n',
|
|
58
|
-
);
|
|
59
|
-
|
|
60
|
-
let insertImport = fixer.insertTextBeforeRange(
|
|
61
|
-
[0, 0],
|
|
62
|
-
`import { mockHttpClient } from 'core/api/mock/jasmine';\n`,
|
|
63
|
-
);
|
|
64
|
-
|
|
65
|
-
// Put after 'use strict'
|
|
66
|
-
const sourcecode = text(program);
|
|
67
|
-
const [preamble] = /^['"]use strict['"];?/.exec(sourcecode) || [];
|
|
68
|
-
if (preamble) {
|
|
69
|
-
const insertPos = preamble.length;
|
|
70
|
-
insertImport = fixer.insertTextAfterRange(
|
|
71
|
-
[insertPos, insertPos],
|
|
72
|
-
`\nimport { mockHttpClient } from 'core/api/mock/jasmine';`,
|
|
73
|
-
);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (mockHttpClientImport) {
|
|
77
|
-
return insertHttp;
|
|
78
|
-
} else {
|
|
79
|
-
return [insertHttp, insertImport];
|
|
80
|
-
}
|
|
81
|
-
},
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Fix 3:
|
|
86
|
-
// - replace "$httpBackend.when('GET'" with "$httpBackend.expectGET("
|
|
87
|
-
// - replace "$httpBackend.whenGET" with "$httpBackend.expectGET"
|
|
88
|
-
// - replace "$httpBackend" with "http"
|
|
89
|
-
return context.report({
|
|
90
|
-
node,
|
|
91
|
-
message: 'Migrate to MockHttpClient (step 3): replace $httpBackend with http',
|
|
92
|
-
fix: (fixer) => {
|
|
93
|
-
const newItBlockText = itBlockText
|
|
94
|
-
.replace(/(this\.)?\$httpBackend\.when(GET|POST|PUT|PATCH|DELETE)/g, '$httpBackend.expect$2')
|
|
95
|
-
.replace(
|
|
96
|
-
/(this\.)?\$httpBackend\.when\(['"](GET|POST|PUT|PATCH|DELETE)['"], /g,
|
|
97
|
-
'$httpBackend.expect$2(',
|
|
98
|
-
)
|
|
99
|
-
.replace(/(this\.)?\$httpBackend/g, 'http')
|
|
100
|
-
.replace(/http.flush/g, 'await http.flush')
|
|
101
|
-
.replace(/await await /g, 'await ');
|
|
102
|
-
|
|
103
|
-
return fixer.replaceText(node, newItBlockText);
|
|
104
|
-
},
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
},
|
|
110
|
-
};
|
|
111
|
-
},
|
|
112
|
-
meta: {
|
|
113
|
-
fixable: 'code',
|
|
114
|
-
type: 'problem',
|
|
115
|
-
docs: {
|
|
116
|
-
description: 'Do not import API',
|
|
117
|
-
},
|
|
118
|
-
},
|
|
119
|
-
};
|
|
120
|
-
|
|
121
|
-
export default ruleModule;
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import rule from './ng-no-component-class';
|
|
2
|
-
import ruleTester from '../utils/ruleTester';
|
|
3
|
-
|
|
4
|
-
ruleTester.run('ng-no-component-class', rule, {
|
|
5
|
-
valid: [
|
|
6
|
-
{
|
|
7
|
-
code: `
|
|
8
|
-
const angular = require('angular');
|
|
9
|
-
angular.module('foo', [])
|
|
10
|
-
.component('componentName', componentObject);
|
|
11
|
-
|
|
12
|
-
const componentObject = {
|
|
13
|
-
controller: function() {},
|
|
14
|
-
template: 'a template'
|
|
15
|
-
}
|
|
16
|
-
`,
|
|
17
|
-
},
|
|
18
|
-
],
|
|
19
|
-
|
|
20
|
-
invalid: [
|
|
21
|
-
{
|
|
22
|
-
errors: [{ message: 'Use .component("foo", {}) instead of .component("foo", new FooComponentClass())' }],
|
|
23
|
-
code: `
|
|
24
|
-
import angular from 'angular';
|
|
25
|
-
angular.module('foo', [])
|
|
26
|
-
.component('componentName', new ComponentClass());
|
|
27
|
-
|
|
28
|
-
class ComponentClass {
|
|
29
|
-
controller = function() {};
|
|
30
|
-
template = 'a template';
|
|
31
|
-
}
|
|
32
|
-
`,
|
|
33
|
-
output: `
|
|
34
|
-
import angular from 'angular';
|
|
35
|
-
angular.module('foo', [])
|
|
36
|
-
.component('componentName', componentClass);
|
|
37
|
-
|
|
38
|
-
const componentClass = {
|
|
39
|
-
controller: function() {},
|
|
40
|
-
template: 'a template'
|
|
41
|
-
};
|
|
42
|
-
`,
|
|
43
|
-
},
|
|
44
|
-
],
|
|
45
|
-
});
|