@akinon/eslint-plugin-projectzero 1.14.0 → 1.15.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/.gitattributes +15 -0
- package/.prettierrc +13 -13
- package/CHANGELOG.md +12 -0
- package/configs/core.ts +20 -20
- package/configs/index.ts +4 -4
- package/configs/recommended.ts +23 -23
- package/package.json +1 -1
- package/rules/check-locale.ts +78 -78
- package/rules/check-middleware-order.ts +64 -64
- package/rules/client-url.ts +44 -44
- package/rules/image-import.ts +44 -44
- package/rules/index.ts +23 -23
- package/rules/link-import.ts +44 -44
- package/rules/meta-data-import.ts +50 -50
- package/rules/skip-trailing-slash-redirect.ts +60 -60
package/.gitattributes
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
*.js text eol=lf
|
|
2
|
+
*.jsx text eol=lf
|
|
3
|
+
*.ts text eol=lf
|
|
4
|
+
*.tsx text eol=lf
|
|
5
|
+
*.json text eol=lf
|
|
6
|
+
*.md text eol=lf
|
|
7
|
+
|
|
8
|
+
.eslintignore text eol=lf
|
|
9
|
+
.eslintrc text eol=lf
|
|
10
|
+
.gitignore text eol=lf
|
|
11
|
+
.prettierrc text eol=lf
|
|
12
|
+
.yarnrc text eol=lf
|
|
13
|
+
|
|
14
|
+
* text=auto
|
|
15
|
+
|
package/.prettierrc
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
{
|
|
2
|
-
"bracketSameLine": false,
|
|
3
|
-
"tabWidth": 2,
|
|
4
|
-
"singleQuote": true,
|
|
5
|
-
"jsxSingleQuote": false,
|
|
6
|
-
"bracketSpacing": true,
|
|
7
|
-
"semi": true,
|
|
8
|
-
"useTabs": false,
|
|
9
|
-
"arrowParens": "always",
|
|
10
|
-
"endOfLine": "lf",
|
|
11
|
-
"proseWrap": "never",
|
|
12
|
-
"trailingComma": "none"
|
|
13
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"bracketSameLine": false,
|
|
3
|
+
"tabWidth": 2,
|
|
4
|
+
"singleQuote": true,
|
|
5
|
+
"jsxSingleQuote": false,
|
|
6
|
+
"bracketSpacing": true,
|
|
7
|
+
"semi": true,
|
|
8
|
+
"useTabs": false,
|
|
9
|
+
"arrowParens": "always",
|
|
10
|
+
"endOfLine": "lf",
|
|
11
|
+
"proseWrap": "never",
|
|
12
|
+
"trailingComma": "none"
|
|
13
|
+
}
|
package/CHANGELOG.md
CHANGED
package/configs/core.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
rules: {
|
|
3
|
-
'@akinon/projectzero/image-import': 'error',
|
|
4
|
-
'@akinon/projectzero/client-url': 'error',
|
|
5
|
-
'@akinon/projectzero/link-import': 'error',
|
|
6
|
-
'@akinon/projectzero/router-import': 'error',
|
|
7
|
-
'@akinon/projectzero/skip-trailing-slash-redirect': 'error',
|
|
8
|
-
'@akinon/projectzero/check-locale': 'error',
|
|
9
|
-
'@akinon/projectzero/meta-data-import': 'error',
|
|
10
|
-
'@akinon/projectzero/urls-without-slash': 'error',
|
|
11
|
-
'no-restricted-syntax': [
|
|
12
|
-
'error',
|
|
13
|
-
{
|
|
14
|
-
selector:
|
|
15
|
-
"MemberExpression[property.name='COMMERCE_URL'][object.property.name='env'][object.object.name='process']",
|
|
16
|
-
message: "You should use it as 'Settings.commerceUrl'"
|
|
17
|
-
}
|
|
18
|
-
]
|
|
19
|
-
}
|
|
20
|
-
};
|
|
1
|
+
export default {
|
|
2
|
+
rules: {
|
|
3
|
+
'@akinon/projectzero/image-import': 'error',
|
|
4
|
+
'@akinon/projectzero/client-url': 'error',
|
|
5
|
+
'@akinon/projectzero/link-import': 'error',
|
|
6
|
+
'@akinon/projectzero/router-import': 'error',
|
|
7
|
+
'@akinon/projectzero/skip-trailing-slash-redirect': 'error',
|
|
8
|
+
'@akinon/projectzero/check-locale': 'error',
|
|
9
|
+
'@akinon/projectzero/meta-data-import': 'error',
|
|
10
|
+
'@akinon/projectzero/urls-without-slash': 'error',
|
|
11
|
+
'no-restricted-syntax': [
|
|
12
|
+
'error',
|
|
13
|
+
{
|
|
14
|
+
selector:
|
|
15
|
+
"MemberExpression[property.name='COMMERCE_URL'][object.property.name='env'][object.object.name='process']",
|
|
16
|
+
message: "You should use it as 'Settings.commerceUrl'"
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
20
|
+
};
|
package/configs/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import core from './core';
|
|
2
|
-
import recommended from './recommended';
|
|
3
|
-
|
|
4
|
-
export { core, recommended };
|
|
1
|
+
import core from './core';
|
|
2
|
+
import recommended from './recommended';
|
|
3
|
+
|
|
4
|
+
export { core, recommended };
|
package/configs/recommended.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
export default {
|
|
2
|
-
rules: {
|
|
3
|
-
'@next/next/no-html-link-for-pages': 'off',
|
|
4
|
-
'@next/next/no-server-import-in-page': 'off',
|
|
5
|
-
'@typescript-eslint/no-var-requires': 0,
|
|
6
|
-
'@typescript-eslint/no-empty-interface': [
|
|
7
|
-
'error',
|
|
8
|
-
{
|
|
9
|
-
allowSingleExtends: false
|
|
10
|
-
}
|
|
11
|
-
],
|
|
12
|
-
'no-console': ['error', { allow: ['error', 'warn'] }],
|
|
13
|
-
'no-empty-function': [
|
|
14
|
-
'error',
|
|
15
|
-
{ allow: ['arrowFunctions', 'functions', 'methods'] }
|
|
16
|
-
],
|
|
17
|
-
'object-shorthand': [
|
|
18
|
-
'error',
|
|
19
|
-
'always',
|
|
20
|
-
{ ignoreConstructors: false, avoidQuotes: true }
|
|
21
|
-
]
|
|
22
|
-
}
|
|
23
|
-
};
|
|
1
|
+
export default {
|
|
2
|
+
rules: {
|
|
3
|
+
'@next/next/no-html-link-for-pages': 'off',
|
|
4
|
+
'@next/next/no-server-import-in-page': 'off',
|
|
5
|
+
'@typescript-eslint/no-var-requires': 0,
|
|
6
|
+
'@typescript-eslint/no-empty-interface': [
|
|
7
|
+
'error',
|
|
8
|
+
{
|
|
9
|
+
allowSingleExtends: false
|
|
10
|
+
}
|
|
11
|
+
],
|
|
12
|
+
'no-console': ['error', { allow: ['error', 'warn'] }],
|
|
13
|
+
'no-empty-function': [
|
|
14
|
+
'error',
|
|
15
|
+
{ allow: ['arrowFunctions', 'functions', 'methods'] }
|
|
16
|
+
],
|
|
17
|
+
'object-shorthand': [
|
|
18
|
+
'error',
|
|
19
|
+
'always',
|
|
20
|
+
{ ignoreConstructors: false, avoidQuotes: true }
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
};
|
package/package.json
CHANGED
package/rules/check-locale.ts
CHANGED
|
@@ -1,78 +1,78 @@
|
|
|
1
|
-
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import * as fs from 'fs';
|
|
4
|
-
|
|
5
|
-
const MONOREPO_LOCALES_DIR = path.resolve(
|
|
6
|
-
__dirname,
|
|
7
|
-
'../../../../apps/projectzeropwa/public/locales/en'
|
|
8
|
-
);
|
|
9
|
-
|
|
10
|
-
const STANDARD_REPO_LOCALES_DIR = path.join(process.cwd(), 'public/locales/en');
|
|
11
|
-
|
|
12
|
-
const LOCALES_DIR = fs.existsSync(MONOREPO_LOCALES_DIR)
|
|
13
|
-
? MONOREPO_LOCALES_DIR
|
|
14
|
-
: STANDARD_REPO_LOCALES_DIR;
|
|
15
|
-
|
|
16
|
-
const getAllKeysFromObject = (obj: any, prefix = ''): string[] => {
|
|
17
|
-
return Object.keys(obj).reduce((result, key) => {
|
|
18
|
-
if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
|
19
|
-
return result.concat(getAllKeysFromObject(obj[key], `${prefix}${key}.`));
|
|
20
|
-
}
|
|
21
|
-
return result.concat(`${prefix}${key}`);
|
|
22
|
-
}, [] as string[]);
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export default ESLintUtils.RuleCreator.withoutDocs({
|
|
26
|
-
create(context: any) {
|
|
27
|
-
return {
|
|
28
|
-
CallExpression(node: any) {
|
|
29
|
-
if (node.callee.type !== 'Identifier' || node.callee.name !== 't') {
|
|
30
|
-
return;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const [firstArg] = node.arguments;
|
|
34
|
-
|
|
35
|
-
if (
|
|
36
|
-
firstArg &&
|
|
37
|
-
firstArg.type === 'Literal' &&
|
|
38
|
-
typeof firstArg.value === 'string'
|
|
39
|
-
) {
|
|
40
|
-
const key = firstArg.value;
|
|
41
|
-
|
|
42
|
-
const filename = `${key.split('.')[0]}.json`;
|
|
43
|
-
const filepath = path.join(LOCALES_DIR, filename);
|
|
44
|
-
|
|
45
|
-
if (fs.existsSync(filepath)) {
|
|
46
|
-
const jsonContent = JSON.parse(fs.readFileSync(filepath, 'utf-8'));
|
|
47
|
-
const allKeys = getAllKeysFromObject(
|
|
48
|
-
jsonContent,
|
|
49
|
-
filename.replace('.json', '.')
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
if (!allKeys.includes(key)) {
|
|
53
|
-
context.report({
|
|
54
|
-
node: firstArg,
|
|
55
|
-
messageId: 'missingLocalizationKey'
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
} else {
|
|
59
|
-
context.report({
|
|
60
|
-
node: firstArg,
|
|
61
|
-
messageId: 'missingLocalizationFile'
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
};
|
|
67
|
-
},
|
|
68
|
-
meta: {
|
|
69
|
-
messages: {
|
|
70
|
-
missingLocalizationKey:
|
|
71
|
-
'The localization key does not exist in the JSON files.',
|
|
72
|
-
missingLocalizationFile: 'The localization file does not exist.'
|
|
73
|
-
},
|
|
74
|
-
type: 'problem',
|
|
75
|
-
schema: []
|
|
76
|
-
},
|
|
77
|
-
defaultOptions: []
|
|
78
|
-
});
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as fs from 'fs';
|
|
4
|
+
|
|
5
|
+
const MONOREPO_LOCALES_DIR = path.resolve(
|
|
6
|
+
__dirname,
|
|
7
|
+
'../../../../apps/projectzeropwa/public/locales/en'
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
const STANDARD_REPO_LOCALES_DIR = path.join(process.cwd(), 'public/locales/en');
|
|
11
|
+
|
|
12
|
+
const LOCALES_DIR = fs.existsSync(MONOREPO_LOCALES_DIR)
|
|
13
|
+
? MONOREPO_LOCALES_DIR
|
|
14
|
+
: STANDARD_REPO_LOCALES_DIR;
|
|
15
|
+
|
|
16
|
+
const getAllKeysFromObject = (obj: any, prefix = ''): string[] => {
|
|
17
|
+
return Object.keys(obj).reduce((result, key) => {
|
|
18
|
+
if (typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
|
|
19
|
+
return result.concat(getAllKeysFromObject(obj[key], `${prefix}${key}.`));
|
|
20
|
+
}
|
|
21
|
+
return result.concat(`${prefix}${key}`);
|
|
22
|
+
}, [] as string[]);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export default ESLintUtils.RuleCreator.withoutDocs({
|
|
26
|
+
create(context: any) {
|
|
27
|
+
return {
|
|
28
|
+
CallExpression(node: any) {
|
|
29
|
+
if (node.callee.type !== 'Identifier' || node.callee.name !== 't') {
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const [firstArg] = node.arguments;
|
|
34
|
+
|
|
35
|
+
if (
|
|
36
|
+
firstArg &&
|
|
37
|
+
firstArg.type === 'Literal' &&
|
|
38
|
+
typeof firstArg.value === 'string'
|
|
39
|
+
) {
|
|
40
|
+
const key = firstArg.value;
|
|
41
|
+
|
|
42
|
+
const filename = `${key.split('.')[0]}.json`;
|
|
43
|
+
const filepath = path.join(LOCALES_DIR, filename);
|
|
44
|
+
|
|
45
|
+
if (fs.existsSync(filepath)) {
|
|
46
|
+
const jsonContent = JSON.parse(fs.readFileSync(filepath, 'utf-8'));
|
|
47
|
+
const allKeys = getAllKeysFromObject(
|
|
48
|
+
jsonContent,
|
|
49
|
+
filename.replace('.json', '.')
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
if (!allKeys.includes(key)) {
|
|
53
|
+
context.report({
|
|
54
|
+
node: firstArg,
|
|
55
|
+
messageId: 'missingLocalizationKey'
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
} else {
|
|
59
|
+
context.report({
|
|
60
|
+
node: firstArg,
|
|
61
|
+
messageId: 'missingLocalizationFile'
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
},
|
|
68
|
+
meta: {
|
|
69
|
+
messages: {
|
|
70
|
+
missingLocalizationKey:
|
|
71
|
+
'The localization key does not exist in the JSON files.',
|
|
72
|
+
missingLocalizationFile: 'The localization file does not exist.'
|
|
73
|
+
},
|
|
74
|
+
type: 'problem',
|
|
75
|
+
schema: []
|
|
76
|
+
},
|
|
77
|
+
defaultOptions: []
|
|
78
|
+
});
|
|
@@ -1,64 +1,64 @@
|
|
|
1
|
-
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
-
|
|
3
|
-
const correctOrder = [
|
|
4
|
-
'withOauthLogin',
|
|
5
|
-
'withLocale',
|
|
6
|
-
'withCurrency',
|
|
7
|
-
'withPrettyUrl',
|
|
8
|
-
'withRedirectionPayment',
|
|
9
|
-
'withThreeDRedirection',
|
|
10
|
-
'withUrlRedirection'
|
|
11
|
-
];
|
|
12
|
-
|
|
13
|
-
export default ESLintUtils.RuleCreator.withoutDocs({
|
|
14
|
-
create(context: any) {
|
|
15
|
-
return {
|
|
16
|
-
CallExpression(node: any) {
|
|
17
|
-
if (
|
|
18
|
-
node.callee.type === 'Identifier' &&
|
|
19
|
-
correctOrder.includes(node.callee.name)
|
|
20
|
-
) {
|
|
21
|
-
let current = node;
|
|
22
|
-
const callSequence = [];
|
|
23
|
-
|
|
24
|
-
while (
|
|
25
|
-
current.type === 'CallExpression' &&
|
|
26
|
-
current.callee.type === 'Identifier'
|
|
27
|
-
) {
|
|
28
|
-
if (correctOrder.includes(current.callee.name)) {
|
|
29
|
-
callSequence.unshift(current.callee.name);
|
|
30
|
-
}
|
|
31
|
-
current = current.parent;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
let isValidOrder = true;
|
|
35
|
-
for (let i = 0; i < callSequence.length; i++) {
|
|
36
|
-
if (callSequence[i] !== correctOrder[i]) {
|
|
37
|
-
isValidOrder = false;
|
|
38
|
-
break;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// Report an error if the order is incorrect
|
|
43
|
-
if (!isValidOrder) {
|
|
44
|
-
context.report({
|
|
45
|
-
node,
|
|
46
|
-
message: `Incorrect order of middleware functions. Expected order: ${correctOrder.join(
|
|
47
|
-
', '
|
|
48
|
-
)}. Found order: ${callSequence.join(', ')}.`
|
|
49
|
-
});
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
};
|
|
54
|
-
},
|
|
55
|
-
meta: {
|
|
56
|
-
type: 'problem',
|
|
57
|
-
schema: [],
|
|
58
|
-
messages: {
|
|
59
|
-
incorrectMiddlewareOrder:
|
|
60
|
-
'Incorrect middleware order. Expected {{expected}}, found {{found}}.'
|
|
61
|
-
}
|
|
62
|
-
},
|
|
63
|
-
defaultOptions: []
|
|
64
|
-
});
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
|
|
3
|
+
const correctOrder = [
|
|
4
|
+
'withOauthLogin',
|
|
5
|
+
'withLocale',
|
|
6
|
+
'withCurrency',
|
|
7
|
+
'withPrettyUrl',
|
|
8
|
+
'withRedirectionPayment',
|
|
9
|
+
'withThreeDRedirection',
|
|
10
|
+
'withUrlRedirection'
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
export default ESLintUtils.RuleCreator.withoutDocs({
|
|
14
|
+
create(context: any) {
|
|
15
|
+
return {
|
|
16
|
+
CallExpression(node: any) {
|
|
17
|
+
if (
|
|
18
|
+
node.callee.type === 'Identifier' &&
|
|
19
|
+
correctOrder.includes(node.callee.name)
|
|
20
|
+
) {
|
|
21
|
+
let current = node;
|
|
22
|
+
const callSequence = [];
|
|
23
|
+
|
|
24
|
+
while (
|
|
25
|
+
current.type === 'CallExpression' &&
|
|
26
|
+
current.callee.type === 'Identifier'
|
|
27
|
+
) {
|
|
28
|
+
if (correctOrder.includes(current.callee.name)) {
|
|
29
|
+
callSequence.unshift(current.callee.name);
|
|
30
|
+
}
|
|
31
|
+
current = current.parent;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let isValidOrder = true;
|
|
35
|
+
for (let i = 0; i < callSequence.length; i++) {
|
|
36
|
+
if (callSequence[i] !== correctOrder[i]) {
|
|
37
|
+
isValidOrder = false;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Report an error if the order is incorrect
|
|
43
|
+
if (!isValidOrder) {
|
|
44
|
+
context.report({
|
|
45
|
+
node,
|
|
46
|
+
message: `Incorrect order of middleware functions. Expected order: ${correctOrder.join(
|
|
47
|
+
', '
|
|
48
|
+
)}. Found order: ${callSequence.join(', ')}.`
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
},
|
|
55
|
+
meta: {
|
|
56
|
+
type: 'problem',
|
|
57
|
+
schema: [],
|
|
58
|
+
messages: {
|
|
59
|
+
incorrectMiddlewareOrder:
|
|
60
|
+
'Incorrect middleware order. Expected {{expected}}, found {{found}}.'
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
defaultOptions: []
|
|
64
|
+
});
|
package/rules/client-url.ts
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
-
|
|
3
|
-
export default ESLintUtils.RuleCreator.withoutDocs({
|
|
4
|
-
create(context) {
|
|
5
|
-
if (!context.getFilename().includes('urls.ts')) {
|
|
6
|
-
return {};
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
return {
|
|
10
|
-
Literal(node) {
|
|
11
|
-
const value = node.value?.toString();
|
|
12
|
-
const isStrLiteral = value?.includes('${');
|
|
13
|
-
|
|
14
|
-
if (!value) {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
if (value.endsWith('/')) {
|
|
19
|
-
context.report({
|
|
20
|
-
messageId: 'clientUrl',
|
|
21
|
-
node,
|
|
22
|
-
fix: (fixer) =>
|
|
23
|
-
fixer.replaceText(
|
|
24
|
-
node,
|
|
25
|
-
`${isStrLiteral ? '`' : `'`}${value.substring(
|
|
26
|
-
0,
|
|
27
|
-
value.length - 1
|
|
28
|
-
)}${isStrLiteral ? '`' : `'`}`
|
|
29
|
-
)
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
},
|
|
35
|
-
meta: {
|
|
36
|
-
messages: {
|
|
37
|
-
clientUrl: 'Client URLs must not end with slash(/).'
|
|
38
|
-
},
|
|
39
|
-
type: 'problem',
|
|
40
|
-
fixable: 'code',
|
|
41
|
-
schema: []
|
|
42
|
-
},
|
|
43
|
-
defaultOptions: []
|
|
44
|
-
});
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
|
|
3
|
+
export default ESLintUtils.RuleCreator.withoutDocs({
|
|
4
|
+
create(context) {
|
|
5
|
+
if (!context.getFilename().includes('urls.ts')) {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
Literal(node) {
|
|
11
|
+
const value = node.value?.toString();
|
|
12
|
+
const isStrLiteral = value?.includes('${');
|
|
13
|
+
|
|
14
|
+
if (!value) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (value.endsWith('/')) {
|
|
19
|
+
context.report({
|
|
20
|
+
messageId: 'clientUrl',
|
|
21
|
+
node,
|
|
22
|
+
fix: (fixer) =>
|
|
23
|
+
fixer.replaceText(
|
|
24
|
+
node,
|
|
25
|
+
`${isStrLiteral ? '`' : `'`}${value.substring(
|
|
26
|
+
0,
|
|
27
|
+
value.length - 1
|
|
28
|
+
)}${isStrLiteral ? '`' : `'`}`
|
|
29
|
+
)
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
meta: {
|
|
36
|
+
messages: {
|
|
37
|
+
clientUrl: 'Client URLs must not end with slash(/).'
|
|
38
|
+
},
|
|
39
|
+
type: 'problem',
|
|
40
|
+
fixable: 'code',
|
|
41
|
+
schema: []
|
|
42
|
+
},
|
|
43
|
+
defaultOptions: []
|
|
44
|
+
});
|
package/rules/image-import.ts
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
-
|
|
3
|
-
export default ESLintUtils.RuleCreator.withoutDocs({
|
|
4
|
-
create(context) {
|
|
5
|
-
if (context.getFilename().includes('components/image.tsx')) {
|
|
6
|
-
return {};
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
return {
|
|
10
|
-
ImportDeclaration(node) {
|
|
11
|
-
if (node.source.value === 'next/image') {
|
|
12
|
-
context.report({
|
|
13
|
-
messageId: 'imageImport',
|
|
14
|
-
node,
|
|
15
|
-
fix: (fixer) => {
|
|
16
|
-
if (node.specifiers.find((s) => s.type === 'ImportSpecifier')) {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return fixer.replaceText(
|
|
21
|
-
node,
|
|
22
|
-
node.specifiers
|
|
23
|
-
.map(
|
|
24
|
-
(specifier) =>
|
|
25
|
-
`import { ${specifier.local.name} } from 'components';`
|
|
26
|
-
)
|
|
27
|
-
.join('\n')
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
},
|
|
35
|
-
meta: {
|
|
36
|
-
messages: {
|
|
37
|
-
imageImport: "Import 'Image' component from 'components'."
|
|
38
|
-
},
|
|
39
|
-
type: 'problem',
|
|
40
|
-
fixable: 'code',
|
|
41
|
-
schema: []
|
|
42
|
-
},
|
|
43
|
-
defaultOptions: []
|
|
44
|
-
});
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
|
|
3
|
+
export default ESLintUtils.RuleCreator.withoutDocs({
|
|
4
|
+
create(context) {
|
|
5
|
+
if (context.getFilename().includes('components/image.tsx')) {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
ImportDeclaration(node) {
|
|
11
|
+
if (node.source.value === 'next/image') {
|
|
12
|
+
context.report({
|
|
13
|
+
messageId: 'imageImport',
|
|
14
|
+
node,
|
|
15
|
+
fix: (fixer) => {
|
|
16
|
+
if (node.specifiers.find((s) => s.type === 'ImportSpecifier')) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return fixer.replaceText(
|
|
21
|
+
node,
|
|
22
|
+
node.specifiers
|
|
23
|
+
.map(
|
|
24
|
+
(specifier) =>
|
|
25
|
+
`import { ${specifier.local.name} } from 'components';`
|
|
26
|
+
)
|
|
27
|
+
.join('\n')
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
meta: {
|
|
36
|
+
messages: {
|
|
37
|
+
imageImport: "Import 'Image' component from 'components'."
|
|
38
|
+
},
|
|
39
|
+
type: 'problem',
|
|
40
|
+
fixable: 'code',
|
|
41
|
+
schema: []
|
|
42
|
+
},
|
|
43
|
+
defaultOptions: []
|
|
44
|
+
});
|
package/rules/index.ts
CHANGED
|
@@ -1,23 +1,23 @@
|
|
|
1
|
-
import clientUrl from './client-url';
|
|
2
|
-
import imageImport from './image-import';
|
|
3
|
-
import linkImport from './link-import';
|
|
4
|
-
import routerImport from './router-import';
|
|
5
|
-
import skipTrailingSlashRedirect from './skip-trailing-slash-redirect';
|
|
6
|
-
import checkLocale from './check-locale';
|
|
7
|
-
import metaDataImport from './meta-data-import';
|
|
8
|
-
import checkMiddlewareOrder from './check-middleware-order';
|
|
9
|
-
import urlsWithoutSlash from './urls-without-slash';
|
|
10
|
-
|
|
11
|
-
const rules = {
|
|
12
|
-
'client-url': clientUrl,
|
|
13
|
-
'image-import': imageImport,
|
|
14
|
-
'link-import': linkImport,
|
|
15
|
-
'router-import': routerImport,
|
|
16
|
-
'skip-trailing-slash-redirect': skipTrailingSlashRedirect,
|
|
17
|
-
'check-locale': checkLocale,
|
|
18
|
-
'meta-data-import': metaDataImport,
|
|
19
|
-
'check-middleware-order': checkMiddlewareOrder,
|
|
20
|
-
'urls-without-slash': urlsWithoutSlash
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export { rules };
|
|
1
|
+
import clientUrl from './client-url';
|
|
2
|
+
import imageImport from './image-import';
|
|
3
|
+
import linkImport from './link-import';
|
|
4
|
+
import routerImport from './router-import';
|
|
5
|
+
import skipTrailingSlashRedirect from './skip-trailing-slash-redirect';
|
|
6
|
+
import checkLocale from './check-locale';
|
|
7
|
+
import metaDataImport from './meta-data-import';
|
|
8
|
+
import checkMiddlewareOrder from './check-middleware-order';
|
|
9
|
+
import urlsWithoutSlash from './urls-without-slash';
|
|
10
|
+
|
|
11
|
+
const rules = {
|
|
12
|
+
'client-url': clientUrl,
|
|
13
|
+
'image-import': imageImport,
|
|
14
|
+
'link-import': linkImport,
|
|
15
|
+
'router-import': routerImport,
|
|
16
|
+
'skip-trailing-slash-redirect': skipTrailingSlashRedirect,
|
|
17
|
+
'check-locale': checkLocale,
|
|
18
|
+
'meta-data-import': metaDataImport,
|
|
19
|
+
'check-middleware-order': checkMiddlewareOrder,
|
|
20
|
+
'urls-without-slash': urlsWithoutSlash
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export { rules };
|
package/rules/link-import.ts
CHANGED
|
@@ -1,44 +1,44 @@
|
|
|
1
|
-
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
-
|
|
3
|
-
export default ESLintUtils.RuleCreator.withoutDocs({
|
|
4
|
-
create(context) {
|
|
5
|
-
if (context.getFilename().includes('components/link.tsx')) {
|
|
6
|
-
return {};
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
return {
|
|
10
|
-
ImportDeclaration(node) {
|
|
11
|
-
if (node.source.value === 'next/link') {
|
|
12
|
-
context.report({
|
|
13
|
-
messageId: 'linkImport',
|
|
14
|
-
node,
|
|
15
|
-
fix: (fixer) => {
|
|
16
|
-
if (node.specifiers.find((s) => s.type === 'ImportSpecifier')) {
|
|
17
|
-
return null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
return fixer.replaceText(
|
|
21
|
-
node,
|
|
22
|
-
node.specifiers
|
|
23
|
-
.map(
|
|
24
|
-
(specifier) =>
|
|
25
|
-
`import { ${specifier.local.name} } from 'components';`
|
|
26
|
-
)
|
|
27
|
-
.join('\n')
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
|
-
},
|
|
35
|
-
meta: {
|
|
36
|
-
messages: {
|
|
37
|
-
linkImport: "Import 'Link' component from 'components'."
|
|
38
|
-
},
|
|
39
|
-
type: 'problem',
|
|
40
|
-
fixable: 'code',
|
|
41
|
-
schema: []
|
|
42
|
-
},
|
|
43
|
-
defaultOptions: []
|
|
44
|
-
});
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
|
|
3
|
+
export default ESLintUtils.RuleCreator.withoutDocs({
|
|
4
|
+
create(context) {
|
|
5
|
+
if (context.getFilename().includes('components/link.tsx')) {
|
|
6
|
+
return {};
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
return {
|
|
10
|
+
ImportDeclaration(node) {
|
|
11
|
+
if (node.source.value === 'next/link') {
|
|
12
|
+
context.report({
|
|
13
|
+
messageId: 'linkImport',
|
|
14
|
+
node,
|
|
15
|
+
fix: (fixer) => {
|
|
16
|
+
if (node.specifiers.find((s) => s.type === 'ImportSpecifier')) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return fixer.replaceText(
|
|
21
|
+
node,
|
|
22
|
+
node.specifiers
|
|
23
|
+
.map(
|
|
24
|
+
(specifier) =>
|
|
25
|
+
`import { ${specifier.local.name} } from 'components';`
|
|
26
|
+
)
|
|
27
|
+
.join('\n')
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
meta: {
|
|
36
|
+
messages: {
|
|
37
|
+
linkImport: "Import 'Link' component from 'components'."
|
|
38
|
+
},
|
|
39
|
+
type: 'problem',
|
|
40
|
+
fixable: 'code',
|
|
41
|
+
schema: []
|
|
42
|
+
},
|
|
43
|
+
defaultOptions: []
|
|
44
|
+
});
|
|
@@ -1,50 +1,50 @@
|
|
|
1
|
-
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
-
|
|
3
|
-
export default ESLintUtils.RuleCreator.withoutDocs({
|
|
4
|
-
create(context) {
|
|
5
|
-
return {
|
|
6
|
-
ImportDeclaration(node) {
|
|
7
|
-
const targetImportName = 'Metadata';
|
|
8
|
-
|
|
9
|
-
const importSpecifiers = node.specifiers
|
|
10
|
-
.filter((specifier) => specifier.type === 'ImportSpecifier')
|
|
11
|
-
.map((specifier) => specifier.local.name);
|
|
12
|
-
|
|
13
|
-
if (
|
|
14
|
-
importSpecifiers.includes(targetImportName) &&
|
|
15
|
-
node.source.value === 'next'
|
|
16
|
-
) {
|
|
17
|
-
context.report({
|
|
18
|
-
messageId: 'metaDataImport',
|
|
19
|
-
node,
|
|
20
|
-
fix: (fixer) => {
|
|
21
|
-
if (node.specifiers.find((s) => s.type === 'ImportSpecifier')) {
|
|
22
|
-
return null;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
return fixer.replaceText(
|
|
26
|
-
node,
|
|
27
|
-
node.specifiers
|
|
28
|
-
.map(
|
|
29
|
-
(specifier) =>
|
|
30
|
-
`import { ${specifier.local.name} } from '@akinon/next/types';`
|
|
31
|
-
)
|
|
32
|
-
.join('\n')
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
};
|
|
39
|
-
},
|
|
40
|
-
meta: {
|
|
41
|
-
messages: {
|
|
42
|
-
metaDataImport:
|
|
43
|
-
"Import '{ Metadata }' component from '@akinon/next/types'."
|
|
44
|
-
},
|
|
45
|
-
type: 'problem',
|
|
46
|
-
fixable: 'code',
|
|
47
|
-
schema: []
|
|
48
|
-
},
|
|
49
|
-
defaultOptions: []
|
|
50
|
-
});
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
|
|
3
|
+
export default ESLintUtils.RuleCreator.withoutDocs({
|
|
4
|
+
create(context) {
|
|
5
|
+
return {
|
|
6
|
+
ImportDeclaration(node) {
|
|
7
|
+
const targetImportName = 'Metadata';
|
|
8
|
+
|
|
9
|
+
const importSpecifiers = node.specifiers
|
|
10
|
+
.filter((specifier) => specifier.type === 'ImportSpecifier')
|
|
11
|
+
.map((specifier) => specifier.local.name);
|
|
12
|
+
|
|
13
|
+
if (
|
|
14
|
+
importSpecifiers.includes(targetImportName) &&
|
|
15
|
+
node.source.value === 'next'
|
|
16
|
+
) {
|
|
17
|
+
context.report({
|
|
18
|
+
messageId: 'metaDataImport',
|
|
19
|
+
node,
|
|
20
|
+
fix: (fixer) => {
|
|
21
|
+
if (node.specifiers.find((s) => s.type === 'ImportSpecifier')) {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return fixer.replaceText(
|
|
26
|
+
node,
|
|
27
|
+
node.specifiers
|
|
28
|
+
.map(
|
|
29
|
+
(specifier) =>
|
|
30
|
+
`import { ${specifier.local.name} } from '@akinon/next/types';`
|
|
31
|
+
)
|
|
32
|
+
.join('\n')
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
},
|
|
40
|
+
meta: {
|
|
41
|
+
messages: {
|
|
42
|
+
metaDataImport:
|
|
43
|
+
"Import '{ Metadata }' component from '@akinon/next/types'."
|
|
44
|
+
},
|
|
45
|
+
type: 'problem',
|
|
46
|
+
fixable: 'code',
|
|
47
|
+
schema: []
|
|
48
|
+
},
|
|
49
|
+
defaultOptions: []
|
|
50
|
+
});
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
import { AST_NODE_TYPES } from '@typescript-eslint/types';
|
|
2
|
-
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
3
|
-
|
|
4
|
-
export default ESLintUtils.RuleCreator.withoutDocs({
|
|
5
|
-
create(context) {
|
|
6
|
-
return {
|
|
7
|
-
VariableDeclarator(node) {
|
|
8
|
-
if (node.id.type !== AST_NODE_TYPES.Identifier) {
|
|
9
|
-
return;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
if (node.id.name !== 'nextConfig') {
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
if (node.init?.type !== AST_NODE_TYPES.ObjectExpression) {
|
|
17
|
-
return;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const skipTrailingSlashRedirectNode = node.init.properties.find(
|
|
21
|
-
(property) =>
|
|
22
|
-
'key' in property &&
|
|
23
|
-
property.key.type === AST_NODE_TYPES.Identifier &&
|
|
24
|
-
property.key.name === 'skipTrailingSlashRedirect'
|
|
25
|
-
);
|
|
26
|
-
|
|
27
|
-
if (
|
|
28
|
-
!skipTrailingSlashRedirectNode ||
|
|
29
|
-
skipTrailingSlashRedirectNode.type !== AST_NODE_TYPES.Property
|
|
30
|
-
) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
if (
|
|
35
|
-
skipTrailingSlashRedirectNode.value.type === AST_NODE_TYPES.Literal &&
|
|
36
|
-
skipTrailingSlashRedirectNode.value.value === true
|
|
37
|
-
) {
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
context.report({
|
|
42
|
-
node: skipTrailingSlashRedirectNode,
|
|
43
|
-
messageId: 'skipTrailingSlashRedirect',
|
|
44
|
-
fix: (fixer) =>
|
|
45
|
-
fixer.replaceText(skipTrailingSlashRedirectNode.value, 'true')
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
},
|
|
50
|
-
meta: {
|
|
51
|
-
messages: {
|
|
52
|
-
skipTrailingSlashRedirect:
|
|
53
|
-
'The `skipTrailingSlashRedirect` value must be true.'
|
|
54
|
-
},
|
|
55
|
-
type: 'problem',
|
|
56
|
-
fixable: 'code',
|
|
57
|
-
schema: []
|
|
58
|
-
},
|
|
59
|
-
defaultOptions: []
|
|
60
|
-
});
|
|
1
|
+
import { AST_NODE_TYPES } from '@typescript-eslint/types';
|
|
2
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
3
|
+
|
|
4
|
+
export default ESLintUtils.RuleCreator.withoutDocs({
|
|
5
|
+
create(context) {
|
|
6
|
+
return {
|
|
7
|
+
VariableDeclarator(node) {
|
|
8
|
+
if (node.id.type !== AST_NODE_TYPES.Identifier) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (node.id.name !== 'nextConfig') {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (node.init?.type !== AST_NODE_TYPES.ObjectExpression) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const skipTrailingSlashRedirectNode = node.init.properties.find(
|
|
21
|
+
(property) =>
|
|
22
|
+
'key' in property &&
|
|
23
|
+
property.key.type === AST_NODE_TYPES.Identifier &&
|
|
24
|
+
property.key.name === 'skipTrailingSlashRedirect'
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
if (
|
|
28
|
+
!skipTrailingSlashRedirectNode ||
|
|
29
|
+
skipTrailingSlashRedirectNode.type !== AST_NODE_TYPES.Property
|
|
30
|
+
) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (
|
|
35
|
+
skipTrailingSlashRedirectNode.value.type === AST_NODE_TYPES.Literal &&
|
|
36
|
+
skipTrailingSlashRedirectNode.value.value === true
|
|
37
|
+
) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
context.report({
|
|
42
|
+
node: skipTrailingSlashRedirectNode,
|
|
43
|
+
messageId: 'skipTrailingSlashRedirect',
|
|
44
|
+
fix: (fixer) =>
|
|
45
|
+
fixer.replaceText(skipTrailingSlashRedirectNode.value, 'true')
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
meta: {
|
|
51
|
+
messages: {
|
|
52
|
+
skipTrailingSlashRedirect:
|
|
53
|
+
'The `skipTrailingSlashRedirect` value must be true.'
|
|
54
|
+
},
|
|
55
|
+
type: 'problem',
|
|
56
|
+
fixable: 'code',
|
|
57
|
+
schema: []
|
|
58
|
+
},
|
|
59
|
+
defaultOptions: []
|
|
60
|
+
});
|