@synergyerp/frontend-standards 1.5.8 → 1.6.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/.husky/commit-msg +0 -3
- package/.husky/pre-commit +0 -3
- package/.husky/pre-push +0 -3
- package/eslint-rules/require-call-comment.mjs +142 -0
- package/eslint.config.js +49 -0
- package/package.json +4 -1
- package/vitest.config.base.js +36 -0
package/.husky/commit-msg
CHANGED
package/.husky/pre-commit
CHANGED
package/.husky/pre-push
CHANGED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
const EXCLUDED_NAMES = new Set([
|
|
2
|
+
'navigate',
|
|
3
|
+
'redirect',
|
|
4
|
+
'dispatch',
|
|
5
|
+
'reload',
|
|
6
|
+
'refresh',
|
|
7
|
+
'toString',
|
|
8
|
+
'toFixed',
|
|
9
|
+
'toLocaleString',
|
|
10
|
+
'preventDefault',
|
|
11
|
+
'stopPropagation',
|
|
12
|
+
]);
|
|
13
|
+
|
|
14
|
+
const ARRAY_METHODS = new Set([
|
|
15
|
+
'map',
|
|
16
|
+
'filter',
|
|
17
|
+
'reduce',
|
|
18
|
+
'forEach',
|
|
19
|
+
'find',
|
|
20
|
+
'some',
|
|
21
|
+
'every',
|
|
22
|
+
'flatMap',
|
|
23
|
+
'flat',
|
|
24
|
+
'includes',
|
|
25
|
+
'indexOf',
|
|
26
|
+
'findIndex',
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
const TEST_FRAMEWORK = new Set([
|
|
30
|
+
'describe',
|
|
31
|
+
'it',
|
|
32
|
+
'test',
|
|
33
|
+
'expect',
|
|
34
|
+
'beforeEach',
|
|
35
|
+
'afterEach',
|
|
36
|
+
'beforeAll',
|
|
37
|
+
'afterAll',
|
|
38
|
+
'vi',
|
|
39
|
+
'jest',
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
function isReactHook(name) {
|
|
43
|
+
return /^use[A-Z]/.test(name);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function isReactSetter(name) {
|
|
47
|
+
return /^set[A-Z]/.test(name);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isTestMethod(name) {
|
|
51
|
+
return TEST_FRAMEWORK.has(name);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getCallName(callee) {
|
|
55
|
+
if (!callee) return null;
|
|
56
|
+
if (callee.type === 'Identifier') return callee.name;
|
|
57
|
+
if (callee.type === 'MemberExpression' && callee.property.type === 'Identifier') {
|
|
58
|
+
return callee.property.name;
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default {
|
|
64
|
+
meta: {
|
|
65
|
+
type: 'suggestion',
|
|
66
|
+
docs: {
|
|
67
|
+
description: 'Require an inline comment before function calls that perform complex logic.',
|
|
68
|
+
},
|
|
69
|
+
messages: {
|
|
70
|
+
missingComment: 'Add a brief inline comment describing what this function call does.',
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
create(context) {
|
|
74
|
+
const sourceCode = context.sourceCode || context.getSourceCode();
|
|
75
|
+
|
|
76
|
+
function hasCommentOnLineAboveOrSame(node) {
|
|
77
|
+
const line = node.loc.start.line;
|
|
78
|
+
const comments = sourceCode.getAllComments();
|
|
79
|
+
return comments.some(
|
|
80
|
+
(comment) => comment.loc.end.line === line || comment.loc.end.line === line - 1
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function isExcludedCall(node) {
|
|
85
|
+
if (node.parent?.type === 'JSXExpressionContainer') return true;
|
|
86
|
+
|
|
87
|
+
const callee = node.callee;
|
|
88
|
+
const name = getCallName(callee);
|
|
89
|
+
if (!name) return true;
|
|
90
|
+
|
|
91
|
+
if (isReactHook(name)) return true;
|
|
92
|
+
if (isReactSetter(name)) return true;
|
|
93
|
+
if (isTestMethod(name)) return true;
|
|
94
|
+
if (EXCLUDED_NAMES.has(name)) return true;
|
|
95
|
+
|
|
96
|
+
if (ARRAY_METHODS.has(name)) {
|
|
97
|
+
if (callee.type === 'MemberExpression') return true;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (
|
|
101
|
+
callee.type === 'MemberExpression' &&
|
|
102
|
+
callee.object?.type === 'Identifier' &&
|
|
103
|
+
callee.object.name === 'console'
|
|
104
|
+
) {
|
|
105
|
+
return true;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
if (
|
|
109
|
+
callee.type === 'MemberExpression' &&
|
|
110
|
+
callee.object?.type === 'Identifier' &&
|
|
111
|
+
callee.object.name === 'Math'
|
|
112
|
+
) {
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function checkCall(node) {
|
|
120
|
+
if (!node || node.type !== 'CallExpression') return;
|
|
121
|
+
if (isExcludedCall(node)) return;
|
|
122
|
+
if (hasCommentOnLineAboveOrSame(node)) return;
|
|
123
|
+
|
|
124
|
+
context.report({ node, messageId: 'missingComment' });
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return {
|
|
128
|
+
'ExpressionStatement > CallExpression'(node) {
|
|
129
|
+
checkCall(node);
|
|
130
|
+
},
|
|
131
|
+
'ExpressionStatement > AwaitExpression > CallExpression'(node) {
|
|
132
|
+
checkCall(node);
|
|
133
|
+
},
|
|
134
|
+
'VariableDeclarator > CallExpression'(node) {
|
|
135
|
+
checkCall(node);
|
|
136
|
+
},
|
|
137
|
+
'VariableDeclarator > AwaitExpression > CallExpression'(node) {
|
|
138
|
+
checkCall(node);
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
},
|
|
142
|
+
};
|
package/eslint.config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import js from '@eslint/js';
|
|
2
2
|
import boundaries from 'eslint-plugin-boundaries';
|
|
3
3
|
import importPlugin from 'eslint-plugin-import';
|
|
4
|
+
import jsdoc from 'eslint-plugin-jsdoc';
|
|
4
5
|
import jsxA11y from 'eslint-plugin-jsx-a11y';
|
|
5
6
|
import prettierPlugin from 'eslint-plugin-prettier/recommended';
|
|
6
7
|
import reactPlugin from 'eslint-plugin-react';
|
|
@@ -9,6 +10,7 @@ import reactRefresh from 'eslint-plugin-react-refresh';
|
|
|
9
10
|
import unicorn from 'eslint-plugin-unicorn';
|
|
10
11
|
import globals from 'globals';
|
|
11
12
|
import tseslint from 'typescript-eslint';
|
|
13
|
+
import requireCallComment from './eslint-rules/require-call-comment.mjs';
|
|
12
14
|
|
|
13
15
|
export default tseslint.config(
|
|
14
16
|
// ===== BASE: Ignore patterns =====
|
|
@@ -22,6 +24,7 @@ export default tseslint.config(
|
|
|
22
24
|
'build/**',
|
|
23
25
|
'scripts/**',
|
|
24
26
|
'vitest.config.base.ts',
|
|
27
|
+
'vitest.config.base.js',
|
|
25
28
|
'tsconfig.base.json',
|
|
26
29
|
'tsconfig.json',
|
|
27
30
|
'**/*.gen.ts',
|
|
@@ -44,9 +47,11 @@ export default tseslint.config(
|
|
|
44
47
|
'react-hooks': reactHooks,
|
|
45
48
|
'react-refresh': reactRefresh,
|
|
46
49
|
import: importPlugin,
|
|
50
|
+
jsdoc: jsdoc,
|
|
47
51
|
unicorn: unicorn,
|
|
48
52
|
'jsx-a11y': jsxA11y,
|
|
49
53
|
boundaries: boundaries,
|
|
54
|
+
custom: { rules: { 'require-call-comment': requireCallComment } },
|
|
50
55
|
},
|
|
51
56
|
languageOptions: {
|
|
52
57
|
ecmaVersion: 'latest',
|
|
@@ -203,6 +208,38 @@ export default tseslint.config(
|
|
|
203
208
|
'react-hooks/exhaustive-deps': 'warn',
|
|
204
209
|
'react-refresh/only-export-components': ['warn', { allowConstantExport: true }],
|
|
205
210
|
|
|
211
|
+
// ===== JSDOC (FRONTEND_STANDARDS.md Section on Documentation) =====
|
|
212
|
+
'jsdoc/require-jsdoc': [
|
|
213
|
+
'error',
|
|
214
|
+
{
|
|
215
|
+
contexts: [
|
|
216
|
+
'ExportNamedDeclaration > VariableDeclarator > ArrowFunctionExpression',
|
|
217
|
+
'ExportNamedDeclaration > FunctionDeclaration',
|
|
218
|
+
'VariableDeclarator[id.type="Identifier"] > ArrowFunctionExpression',
|
|
219
|
+
'VariableDeclarator[id.type="Identifier"] > FunctionExpression',
|
|
220
|
+
'FunctionDeclaration',
|
|
221
|
+
'MethodDefinition',
|
|
222
|
+
],
|
|
223
|
+
checkConstructors: false,
|
|
224
|
+
enableFixer: false,
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
'jsdoc/require-description': ['error', { contexts: ['any'] }],
|
|
228
|
+
'jsdoc/no-missing-syntax': 'off',
|
|
229
|
+
'jsdoc/multiline-blocks': ['error', { noMultilineBlocks: true, noSingleLineBlocks: true }],
|
|
230
|
+
'jsdoc/require-param': ['error', { contexts: ['any'] }],
|
|
231
|
+
'jsdoc/require-param-name': 'error',
|
|
232
|
+
'jsdoc/require-param-type': 'off',
|
|
233
|
+
'jsdoc/require-returns': ['error', { contexts: ['any'] }],
|
|
234
|
+
'jsdoc/require-returns-type': 'off',
|
|
235
|
+
'jsdoc/require-returns-check': 'error',
|
|
236
|
+
'jsdoc/check-tag-names': 'error',
|
|
237
|
+
'jsdoc/check-types': 'error',
|
|
238
|
+
'jsdoc/empty-tags': 'error',
|
|
239
|
+
|
|
240
|
+
// ===== DOCUMENTATION: Inline call comments =====
|
|
241
|
+
'custom/require-call-comment': 'error',
|
|
242
|
+
|
|
206
243
|
// ===== IMPORTS (FRONTEND_STANDARDS.md Section 5.3) =====
|
|
207
244
|
'import/order': [
|
|
208
245
|
'error',
|
|
@@ -332,6 +369,12 @@ export default tseslint.config(
|
|
|
332
369
|
'no-console': 'off',
|
|
333
370
|
'@typescript-eslint/no-explicit-any': 'off',
|
|
334
371
|
'import/no-internal-modules': 'off',
|
|
372
|
+
'custom/require-call-comment': 'off',
|
|
373
|
+
'jsdoc/require-jsdoc': 'off',
|
|
374
|
+
'jsdoc/require-description': 'off',
|
|
375
|
+
'jsdoc/require-param': 'off',
|
|
376
|
+
'jsdoc/require-returns': 'off',
|
|
377
|
+
'jsdoc/require-returns-check': 'off',
|
|
335
378
|
},
|
|
336
379
|
},
|
|
337
380
|
|
|
@@ -341,6 +384,12 @@ export default tseslint.config(
|
|
|
341
384
|
rules: {
|
|
342
385
|
'@typescript-eslint/no-explicit-any': 'off',
|
|
343
386
|
'import/no-internal-modules': 'off',
|
|
387
|
+
'custom/require-call-comment': 'off',
|
|
388
|
+
'jsdoc/require-jsdoc': 'off',
|
|
389
|
+
'jsdoc/require-description': 'off',
|
|
390
|
+
'jsdoc/require-param': 'off',
|
|
391
|
+
'jsdoc/require-returns': 'off',
|
|
392
|
+
'jsdoc/require-returns-check': 'off',
|
|
344
393
|
},
|
|
345
394
|
}
|
|
346
395
|
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@synergyerp/frontend-standards",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "SynergyERP frontend standards — ESLint, Prettier, commitlint, Husky, Vitest, TypeScript configs, modularization enforcement, and PR templates.",
|
|
5
5
|
"private": false,
|
|
6
6
|
"type": "module",
|
|
@@ -11,11 +11,13 @@
|
|
|
11
11
|
},
|
|
12
12
|
"files": [
|
|
13
13
|
"eslint.config.js",
|
|
14
|
+
"eslint-rules/",
|
|
14
15
|
"prettier.config.js",
|
|
15
16
|
".prettierignore",
|
|
16
17
|
"commitlint.config.js",
|
|
17
18
|
"tsconfig.base.json",
|
|
18
19
|
"vitest.config.base.ts",
|
|
20
|
+
"vitest.config.base.js",
|
|
19
21
|
"scripts/",
|
|
20
22
|
".husky/",
|
|
21
23
|
".vscode/",
|
|
@@ -58,6 +60,7 @@
|
|
|
58
60
|
"eslint-plugin-react": "^7.37.0",
|
|
59
61
|
"eslint-plugin-react-hooks": "^5.0.0",
|
|
60
62
|
"eslint-plugin-react-refresh": "^0.4.0",
|
|
63
|
+
"eslint-plugin-jsdoc": "^50.0.0",
|
|
61
64
|
"eslint-plugin-unicorn": "^56.0.0",
|
|
62
65
|
"globals": "^15.0.0",
|
|
63
66
|
"prettier-plugin-organize-imports": "^4.0.0",
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import react from '@vitejs/plugin-react';
|
|
2
|
+
import tsconfigPaths from 'vite-tsconfig-paths';
|
|
3
|
+
import { defineConfig } from 'vitest/config';
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [react(), tsconfigPaths()],
|
|
7
|
+
test: {
|
|
8
|
+
globals: true,
|
|
9
|
+
environment: 'jsdom',
|
|
10
|
+
setupFiles: ['./src/test/setup.ts'],
|
|
11
|
+
include: ['src/**/*.{test,spec}.{ts,tsx}'],
|
|
12
|
+
exclude: ['node_modules', 'dist', 'build'],
|
|
13
|
+
css: true,
|
|
14
|
+
coverage: {
|
|
15
|
+
provider: 'v8',
|
|
16
|
+
reporter: ['text', 'json', 'html', 'cobertura'],
|
|
17
|
+
include: ['src/**/*.{ts,tsx}'],
|
|
18
|
+
exclude: [
|
|
19
|
+
'src/**/*.test.{ts,tsx}',
|
|
20
|
+
'src/**/*.spec.{ts,tsx}',
|
|
21
|
+
'src/**/*.d.ts',
|
|
22
|
+
'src/test/**',
|
|
23
|
+
'src/mocks/**',
|
|
24
|
+
'src/main.tsx',
|
|
25
|
+
'src/vite-env.d.ts',
|
|
26
|
+
],
|
|
27
|
+
thresholds: {
|
|
28
|
+
lines: 80,
|
|
29
|
+
branches: 75,
|
|
30
|
+
functions: 80,
|
|
31
|
+
statements: 80,
|
|
32
|
+
perFile: true,
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
});
|