@futdevpro/dynamo-eslint 1.12.1
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/.eslintrc.json +16 -0
- package/.github/workflows/main.yml +393 -0
- package/INTEGRATION.md +74 -0
- package/POC-README.md +147 -0
- package/README.md +36 -0
- package/build/configs/base.d.ts +81 -0
- package/build/configs/base.d.ts.map +1 -0
- package/build/configs/base.js +51 -0
- package/build/configs/base.js.map +1 -0
- package/build/configs/fsm.d.ts +82 -0
- package/build/configs/fsm.d.ts.map +1 -0
- package/build/configs/fsm.js +7 -0
- package/build/configs/fsm.js.map +1 -0
- package/build/configs/ngx-package.d.ts +81 -0
- package/build/configs/ngx-package.d.ts.map +1 -0
- package/build/configs/ngx-package.js +7 -0
- package/build/configs/ngx-package.js.map +1 -0
- package/build/configs/ngx.d.ts +81 -0
- package/build/configs/ngx.d.ts.map +1 -0
- package/build/configs/ngx.js +12 -0
- package/build/configs/ngx.js.map +1 -0
- package/build/configs/nts-package.d.ts +83 -0
- package/build/configs/nts-package.d.ts.map +1 -0
- package/build/configs/nts-package.js +12 -0
- package/build/configs/nts-package.js.map +1 -0
- package/build/configs/nts.d.ts +82 -0
- package/build/configs/nts.d.ts.map +1 -0
- package/build/configs/nts.js +13 -0
- package/build/configs/nts.js.map +1 -0
- package/build/index.d.ts +2 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +3 -0
- package/build/index.js.map +1 -0
- package/build/plugin/index.d.ts +16 -0
- package/build/plugin/index.d.ts.map +1 -0
- package/build/plugin/index.js +19 -0
- package/build/plugin/index.js.map +1 -0
- package/build/plugin/rules/import-order.d.ts +4 -0
- package/build/plugin/rules/import-order.d.ts.map +1 -0
- package/build/plugin/rules/import-order.js +134 -0
- package/build/plugin/rules/import-order.js.map +1 -0
- package/build/plugin/rules/import-order.spec.d.ts +2 -0
- package/build/plugin/rules/import-order.spec.d.ts.map +1 -0
- package/build/plugin/rules/import-order.spec.js +181 -0
- package/build/plugin/rules/import-order.spec.js.map +1 -0
- package/build/plugin/rules/naming-patterns.d.ts +4 -0
- package/build/plugin/rules/naming-patterns.d.ts.map +1 -0
- package/build/plugin/rules/naming-patterns.js +23 -0
- package/build/plugin/rules/naming-patterns.js.map +1 -0
- package/build/plugin/rules/naming-patterns.spec.d.ts +2 -0
- package/build/plugin/rules/naming-patterns.spec.d.ts.map +1 -0
- package/build/plugin/rules/naming-patterns.spec.js +36 -0
- package/build/plugin/rules/naming-patterns.spec.js.map +1 -0
- package/build/scripts/eslintrc-audit.d.ts +3 -0
- package/build/scripts/eslintrc-audit.d.ts.map +1 -0
- package/build/scripts/eslintrc-audit.js +36 -0
- package/build/scripts/eslintrc-audit.js.map +1 -0
- package/build/scripts/validate-imports.d.ts +3 -0
- package/build/scripts/validate-imports.d.ts.map +1 -0
- package/build/scripts/validate-imports.js +76 -0
- package/build/scripts/validate-imports.js.map +1 -0
- package/build/scripts/validate-naming.d.ts +3 -0
- package/build/scripts/validate-naming.d.ts.map +1 -0
- package/build/scripts/validate-naming.js +76 -0
- package/build/scripts/validate-naming.js.map +1 -0
- package/build-test/plugin/rules/import-order.d.ts +3 -0
- package/build-test/plugin/rules/import-order.js +23 -0
- package/build-test/plugin/rules/import-order.spec.d.ts +1 -0
- package/build-test/plugin/rules/import-order.spec.js +44 -0
- package/build-test/plugin/rules/naming-patterns.d.ts +3 -0
- package/build-test/plugin/rules/naming-patterns.js +22 -0
- package/build-test/plugin/rules/naming-patterns.spec.d.ts +1 -0
- package/build-test/plugin/rules/naming-patterns.spec.js +41 -0
- package/futdevpro-dynamo-eslint-01.12.01.tgz +0 -0
- package/package.json +95 -0
- package/samples/base/.eslintrc.json +6 -0
- package/samples/ngx/.eslintrc.json +6 -0
- package/samples/nts/.eslintrc.json +6 -0
- package/samples/poc-sample.ts +38 -0
- package/samples/poc-violations.ts +25 -0
- package/spec/support/jasmine.json +24 -0
- package/src/configs/base.ts +51 -0
- package/src/configs/fsm.ts +9 -0
- package/src/configs/ngx-package.ts +9 -0
- package/src/configs/ngx.ts +14 -0
- package/src/configs/nts-package.ts +14 -0
- package/src/configs/nts.ts +15 -0
- package/src/index.ts +4 -0
- package/src/plugin/index.ts +19 -0
- package/src/plugin/rules/import-order.spec.ts +197 -0
- package/src/plugin/rules/import-order.ts +167 -0
- package/src/plugin/rules/naming-patterns.spec.ts +39 -0
- package/src/plugin/rules/naming-patterns.ts +25 -0
- package/src/scripts/eslintrc-audit.ts +39 -0
- package/src/scripts/validate-imports.ts +98 -0
- package/src/scripts/validate-naming.ts +97 -0
- package/tsconfig.json +32 -0
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
import importOrderRule from './import-order';
|
|
2
|
+
|
|
3
|
+
describe('| import-order', () => {
|
|
4
|
+
it('| should be a valid ESLint rule', () => {
|
|
5
|
+
expect(importOrderRule.meta?.type).toBe('suggestion');
|
|
6
|
+
expect(importOrderRule.meta?.docs?.description).toContain('import ordering');
|
|
7
|
+
expect(importOrderRule.meta?.fixable).toBe('code');
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it('| should have create function that returns object with Program method', () => {
|
|
11
|
+
const mockContext = {
|
|
12
|
+
getSourceCode: () => ({
|
|
13
|
+
ast: {},
|
|
14
|
+
getLines: () => ['line1', 'line2', 'line3']
|
|
15
|
+
}),
|
|
16
|
+
getFilename: () => 'test.ts',
|
|
17
|
+
report: () => {},
|
|
18
|
+
} as any;
|
|
19
|
+
|
|
20
|
+
const result = importOrderRule.create(mockContext);
|
|
21
|
+
expect(typeof result).toBe('object');
|
|
22
|
+
expect(typeof result.Program).toBe('function');
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('| should detect forbidden NPM-packages imports', () => {
|
|
26
|
+
const mockContext = {
|
|
27
|
+
getSourceCode: () => ({
|
|
28
|
+
ast: {},
|
|
29
|
+
getLines: () => ['import { Something } from \'../../../NPM-packages/some-package\';']
|
|
30
|
+
}),
|
|
31
|
+
getFilename: () => 'test.ts',
|
|
32
|
+
report: (options: any) => {
|
|
33
|
+
expect(options.messageId).toBe('forbiddenNpmPackages');
|
|
34
|
+
},
|
|
35
|
+
} as any;
|
|
36
|
+
|
|
37
|
+
const mockNode = {
|
|
38
|
+
type: 'Program',
|
|
39
|
+
sourceType: 'module',
|
|
40
|
+
body: [
|
|
41
|
+
{
|
|
42
|
+
type: 'ImportDeclaration',
|
|
43
|
+
source: { value: '../../../NPM-packages/some-package' },
|
|
44
|
+
importKind: 'value',
|
|
45
|
+
loc: { start: { line: 1 }, end: { line: 1 } }
|
|
46
|
+
}
|
|
47
|
+
]
|
|
48
|
+
} as any;
|
|
49
|
+
|
|
50
|
+
const rule = importOrderRule.create(mockContext);
|
|
51
|
+
rule.Program(mockNode);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('| should detect forbidden .js extension imports', () => {
|
|
55
|
+
const mockContext = {
|
|
56
|
+
getSourceCode: () => ({
|
|
57
|
+
ast: {},
|
|
58
|
+
getLines: () => ['import { Something } from \'./some-file.js\';']
|
|
59
|
+
}),
|
|
60
|
+
getFilename: () => 'test.ts',
|
|
61
|
+
report: (options: any) => {
|
|
62
|
+
expect(options.messageId).toBe('forbiddenJsExtension');
|
|
63
|
+
},
|
|
64
|
+
} as any;
|
|
65
|
+
|
|
66
|
+
const mockNode = {
|
|
67
|
+
type: 'Program',
|
|
68
|
+
sourceType: 'module',
|
|
69
|
+
body: [
|
|
70
|
+
{
|
|
71
|
+
type: 'ImportDeclaration',
|
|
72
|
+
source: { value: './some-file.js' },
|
|
73
|
+
importKind: 'value',
|
|
74
|
+
loc: { start: { line: 1 }, end: { line: 1 } }
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
} as any;
|
|
78
|
+
|
|
79
|
+
const rule = importOrderRule.create(mockContext);
|
|
80
|
+
rule.Program(mockNode);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('| should detect forbidden import type usage', () => {
|
|
84
|
+
const mockContext = {
|
|
85
|
+
getSourceCode: () => ({
|
|
86
|
+
ast: {},
|
|
87
|
+
getLines: () => ['import type { SomeType } from \'./some-file\';']
|
|
88
|
+
}),
|
|
89
|
+
getFilename: () => 'test.ts',
|
|
90
|
+
report: (options: any) => {
|
|
91
|
+
expect(options.messageId).toBe('forbiddenImportType');
|
|
92
|
+
},
|
|
93
|
+
} as any;
|
|
94
|
+
|
|
95
|
+
const mockNode = {
|
|
96
|
+
type: 'Program',
|
|
97
|
+
sourceType: 'module',
|
|
98
|
+
body: [
|
|
99
|
+
{
|
|
100
|
+
type: 'ImportDeclaration',
|
|
101
|
+
source: { value: './some-file' },
|
|
102
|
+
importKind: 'type',
|
|
103
|
+
loc: { start: { line: 1 }, end: { line: 1 } }
|
|
104
|
+
}
|
|
105
|
+
]
|
|
106
|
+
} as any;
|
|
107
|
+
|
|
108
|
+
const rule = importOrderRule.create(mockContext);
|
|
109
|
+
rule.Program(mockNode);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it('| should detect misordered imports', () => {
|
|
113
|
+
let reportCount = 0;
|
|
114
|
+
const mockContext = {
|
|
115
|
+
getSourceCode: () => ({
|
|
116
|
+
ast: {},
|
|
117
|
+
getLines: () => [
|
|
118
|
+
'import { DyFM_Error } from \'@futdevpro/fsm-dynamo\';',
|
|
119
|
+
'import { Component } from \'@angular/core\';'
|
|
120
|
+
]
|
|
121
|
+
}),
|
|
122
|
+
getFilename: () => 'test.ts',
|
|
123
|
+
report: (options: any) => {
|
|
124
|
+
if (options.messageId === 'misordered') {
|
|
125
|
+
reportCount++;
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
} as any;
|
|
129
|
+
|
|
130
|
+
const mockNode = {
|
|
131
|
+
type: 'Program',
|
|
132
|
+
sourceType: 'module',
|
|
133
|
+
body: [
|
|
134
|
+
{
|
|
135
|
+
type: 'ImportDeclaration',
|
|
136
|
+
source: { value: '@futdevpro/fsm-dynamo' },
|
|
137
|
+
importKind: 'value',
|
|
138
|
+
loc: { start: { line: 1 }, end: { line: 1 } }
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
type: 'ImportDeclaration',
|
|
142
|
+
source: { value: '@angular/core' },
|
|
143
|
+
importKind: 'value',
|
|
144
|
+
loc: { start: { line: 2 }, end: { line: 2 } }
|
|
145
|
+
}
|
|
146
|
+
]
|
|
147
|
+
} as any;
|
|
148
|
+
|
|
149
|
+
const rule = importOrderRule.create(mockContext);
|
|
150
|
+
rule.Program(mockNode);
|
|
151
|
+
|
|
152
|
+
expect(reportCount).toBeGreaterThan(0);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('| should detect missing empty line between groups', () => {
|
|
156
|
+
let reportCount = 0;
|
|
157
|
+
const mockContext = {
|
|
158
|
+
getSourceCode: () => ({
|
|
159
|
+
ast: {},
|
|
160
|
+
getLines: () => [
|
|
161
|
+
'import { Component } from \'@angular/core\';',
|
|
162
|
+
'import { DyFM_Error } from \'@futdevpro/fsm-dynamo\';'
|
|
163
|
+
]
|
|
164
|
+
}),
|
|
165
|
+
getFilename: () => 'test.ts',
|
|
166
|
+
report: (options: any) => {
|
|
167
|
+
if (options.messageId === 'missingEmptyLine') {
|
|
168
|
+
reportCount++;
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
} as any;
|
|
172
|
+
|
|
173
|
+
const mockNode = {
|
|
174
|
+
type: 'Program',
|
|
175
|
+
sourceType: 'module',
|
|
176
|
+
body: [
|
|
177
|
+
{
|
|
178
|
+
type: 'ImportDeclaration',
|
|
179
|
+
source: { value: '@angular/core' },
|
|
180
|
+
importKind: 'value',
|
|
181
|
+
loc: { start: { line: 1 }, end: { line: 1 } }
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
type: 'ImportDeclaration',
|
|
185
|
+
source: { value: '@futdevpro/fsm-dynamo' },
|
|
186
|
+
importKind: 'value',
|
|
187
|
+
loc: { start: { line: 2 }, end: { line: 2 } }
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
} as any;
|
|
191
|
+
|
|
192
|
+
const rule = importOrderRule.create(mockContext);
|
|
193
|
+
rule.Program(mockNode);
|
|
194
|
+
|
|
195
|
+
expect(reportCount).toBeGreaterThan(0);
|
|
196
|
+
});
|
|
197
|
+
});
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { Rule } from 'eslint';
|
|
2
|
+
|
|
3
|
+
interface ImportGroup {
|
|
4
|
+
type: 'non-futdevpro' | 'futdevpro' | 'other-modules' | 'same-module';
|
|
5
|
+
imports: any[];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const rule: Rule.RuleModule = {
|
|
9
|
+
meta: {
|
|
10
|
+
type: 'suggestion',
|
|
11
|
+
docs: { description: 'Validate import ordering based on Dynamo conventions' },
|
|
12
|
+
schema: [],
|
|
13
|
+
messages: {
|
|
14
|
+
misordered: 'Import statements should be grouped and ordered by convention.',
|
|
15
|
+
forbiddenNpmPackages: 'Import from "*/../NPM-packages/*" is forbidden. Use "@futdevpro/*" instead.',
|
|
16
|
+
forbiddenJsExtension: 'Import with ".js" extension is forbidden. Use ".ts" or no extension.',
|
|
17
|
+
forbiddenImportType: 'Use of "import type" is forbidden.',
|
|
18
|
+
missingEmptyLine: 'Missing empty line between import groups.',
|
|
19
|
+
extraEmptyLine: 'Extra empty line detected.',
|
|
20
|
+
},
|
|
21
|
+
fixable: 'code',
|
|
22
|
+
},
|
|
23
|
+
create(context) {
|
|
24
|
+
const sourceCode = context.getSourceCode();
|
|
25
|
+
const filename = context.getFilename();
|
|
26
|
+
|
|
27
|
+
function getImportGroup(importNode: any): ImportGroup['type'] {
|
|
28
|
+
const source = importNode.source.value as string;
|
|
29
|
+
|
|
30
|
+
// Check for forbidden patterns first
|
|
31
|
+
if (source.includes('/NPM-packages/')) {
|
|
32
|
+
return 'non-futdevpro'; // Will be flagged as error
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (source.endsWith('.js')) {
|
|
36
|
+
return 'non-futdevpro'; // Will be flagged as error
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// FutDevPro packages
|
|
40
|
+
if (source.startsWith('@futdevpro/')) {
|
|
41
|
+
return 'futdevpro';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Same module (relative imports with specific patterns)
|
|
45
|
+
if (source.startsWith('./') || source.startsWith('../')) {
|
|
46
|
+
const pathParts = source.split('/');
|
|
47
|
+
const upLevels = pathParts.filter(part => part === '..').length;
|
|
48
|
+
|
|
49
|
+
// Same module: up to 3 ".." and starts with "_", or less than 2 ".."
|
|
50
|
+
if (upLevels <= 2 || (upLevels <= 3 && pathParts.some(part => part.startsWith('_')))) {
|
|
51
|
+
return 'same-module';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Other modules
|
|
55
|
+
return 'other-modules';
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Non-FutDevPro packages
|
|
59
|
+
return 'non-futdevpro';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function validateImportDeclaration(importNode: any) {
|
|
63
|
+
const source = importNode.source.value as string;
|
|
64
|
+
|
|
65
|
+
// Check for forbidden patterns
|
|
66
|
+
if (source.includes('/NPM-packages/')) {
|
|
67
|
+
context.report({
|
|
68
|
+
node: importNode.source,
|
|
69
|
+
messageId: 'forbiddenNpmPackages',
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (source.endsWith('.js')) {
|
|
74
|
+
context.report({
|
|
75
|
+
node: importNode.source,
|
|
76
|
+
messageId: 'forbiddenJsExtension',
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check for import type usage
|
|
81
|
+
if (importNode.importKind === 'type') {
|
|
82
|
+
context.report({
|
|
83
|
+
node: importNode,
|
|
84
|
+
messageId: 'forbiddenImportType',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function validateImportOrdering(importNodes: any[]) {
|
|
90
|
+
if (importNodes.length === 0) return;
|
|
91
|
+
|
|
92
|
+
// Group imports
|
|
93
|
+
const groups: ImportGroup[] = [
|
|
94
|
+
{ type: 'non-futdevpro', imports: [] },
|
|
95
|
+
{ type: 'futdevpro', imports: [] },
|
|
96
|
+
{ type: 'other-modules', imports: [] },
|
|
97
|
+
{ type: 'same-module', imports: [] },
|
|
98
|
+
];
|
|
99
|
+
|
|
100
|
+
importNodes.forEach(importNode => {
|
|
101
|
+
const groupType = getImportGroup(importNode);
|
|
102
|
+
const group = groups.find(g => g.type === groupType);
|
|
103
|
+
if (group) {
|
|
104
|
+
group.imports.push(importNode);
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// Remove empty groups
|
|
109
|
+
const nonEmptyGroups = groups.filter(group => group.imports.length > 0);
|
|
110
|
+
|
|
111
|
+
// Check if imports are in correct order
|
|
112
|
+
let currentGroupIndex = 0;
|
|
113
|
+
for (let i = 0; i < importNodes.length; i++) {
|
|
114
|
+
const importNode = importNodes[i];
|
|
115
|
+
const expectedGroupType = getImportGroup(importNode);
|
|
116
|
+
const expectedGroup = nonEmptyGroups.find(g => g.type === expectedGroupType);
|
|
117
|
+
const expectedGroupIndex = nonEmptyGroups.findIndex(g => g.type === expectedGroupType);
|
|
118
|
+
|
|
119
|
+
if (expectedGroupIndex < currentGroupIndex) {
|
|
120
|
+
context.report({
|
|
121
|
+
node: importNode,
|
|
122
|
+
messageId: 'misordered',
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
currentGroupIndex = expectedGroupIndex;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Check empty lines between groups
|
|
130
|
+
for (let i = 0; i < nonEmptyGroups.length - 1; i++) {
|
|
131
|
+
const currentGroup = nonEmptyGroups[i];
|
|
132
|
+
const nextGroup = nonEmptyGroups[i + 1];
|
|
133
|
+
const lastImportOfCurrentGroup = currentGroup.imports[currentGroup.imports.length - 1];
|
|
134
|
+
const firstImportOfNextGroup = nextGroup.imports[0];
|
|
135
|
+
|
|
136
|
+
const lines = sourceCode.getLines();
|
|
137
|
+
const lastImportLine = lastImportOfCurrentGroup.loc?.end.line || 0;
|
|
138
|
+
const firstImportLine = firstImportOfNextGroup.loc?.start.line || 0;
|
|
139
|
+
|
|
140
|
+
if (firstImportLine - lastImportLine < 2) {
|
|
141
|
+
context.report({
|
|
142
|
+
node: firstImportOfNextGroup,
|
|
143
|
+
messageId: 'missingEmptyLine',
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return {
|
|
150
|
+
Program(node: any) {
|
|
151
|
+
const importNodes = node.body.filter(
|
|
152
|
+
(statement: any) => statement.type === 'ImportDeclaration'
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// Validate individual import declarations
|
|
156
|
+
importNodes.forEach(validateImportDeclaration);
|
|
157
|
+
|
|
158
|
+
// Validate import ordering
|
|
159
|
+
validateImportOrdering(importNodes);
|
|
160
|
+
},
|
|
161
|
+
};
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
export default rule;
|
|
166
|
+
|
|
167
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import namingPatternsRule from './naming-patterns';
|
|
2
|
+
|
|
3
|
+
describe('| naming-patterns', () => {
|
|
4
|
+
it('| should be a valid ESLint rule', () => {
|
|
5
|
+
expect(namingPatternsRule.meta?.type).toBe('suggestion');
|
|
6
|
+
expect(namingPatternsRule.meta?.docs?.description).toContain('naming conventions');
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
it('| should have create function that returns object with Identifier method', () => {
|
|
10
|
+
const mockContext = {
|
|
11
|
+
report: () => {},
|
|
12
|
+
} as any;
|
|
13
|
+
|
|
14
|
+
const result = namingPatternsRule.create(mockContext);
|
|
15
|
+
expect(typeof result).toBe('object');
|
|
16
|
+
expect(typeof result.Identifier).toBe('function');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('| should handle valid identifiers without errors', () => {
|
|
20
|
+
const mockContext = {
|
|
21
|
+
report: (options: any) => {
|
|
22
|
+
// Should not be called for valid identifiers
|
|
23
|
+
fail('Should not report errors for valid identifiers');
|
|
24
|
+
},
|
|
25
|
+
} as any;
|
|
26
|
+
|
|
27
|
+
const mockNode = {
|
|
28
|
+
type: 'Identifier' as const,
|
|
29
|
+
name: 'validVariableName',
|
|
30
|
+
loc: { start: { line: 1 }, end: { line: 1 } }
|
|
31
|
+
} as any;
|
|
32
|
+
|
|
33
|
+
const rule = namingPatternsRule.create(mockContext);
|
|
34
|
+
rule.Identifier(mockNode);
|
|
35
|
+
|
|
36
|
+
// If we get here without the report function being called, the test passes
|
|
37
|
+
expect(true).toBe(true);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Rule } from 'eslint';
|
|
2
|
+
// import * as dyfmUtils from '@futdevpro/fsm-dynamo';
|
|
3
|
+
|
|
4
|
+
const rule: Rule.RuleModule = {
|
|
5
|
+
meta: {
|
|
6
|
+
type: 'suggestion',
|
|
7
|
+
docs: { description: 'Enforce naming conventions using DyFM patterns' },
|
|
8
|
+
schema: [],
|
|
9
|
+
messages: {
|
|
10
|
+
invalidName: 'Identifier "{{name}}" does not match required naming pattern.',
|
|
11
|
+
},
|
|
12
|
+
},
|
|
13
|
+
create(context) {
|
|
14
|
+
return {
|
|
15
|
+
Identifier(node) {
|
|
16
|
+
// Future: use dyfmUtils naming patterns to validate identifiers
|
|
17
|
+
void node;
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export default rule;
|
|
24
|
+
|
|
25
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fg from 'fast-glob';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
|
|
5
|
+
type AnyJson = any;
|
|
6
|
+
|
|
7
|
+
function safeParse(jsonPath: string): AnyJson | null {
|
|
8
|
+
try {
|
|
9
|
+
const content = readFileSync(jsonPath, 'utf8');
|
|
10
|
+
return JSON.parse(content);
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function main() {
|
|
17
|
+
const files = await fg(['**/.eslintrc.json'], { ignore: ['**/node_modules/**'] });
|
|
18
|
+
const ruleCounts: Record<string, number> = {};
|
|
19
|
+
const total = files.length;
|
|
20
|
+
|
|
21
|
+
for (const p of files) {
|
|
22
|
+
const cfg = safeParse(p);
|
|
23
|
+
if (!cfg?.rules) continue;
|
|
24
|
+
for (const key of Object.keys(cfg.rules)) {
|
|
25
|
+
ruleCounts[key] = (ruleCounts[key] ?? 0) + 1;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const entries = Object.entries(ruleCounts).sort((a, b) => b[1] - a[1]);
|
|
30
|
+
console.log(`[dynamo-eslintrc-audit] analyzed ${total} files`);
|
|
31
|
+
console.table(entries.slice(0, 50).map(([rule, count]) => ({ rule, count })));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
main().catch((err) => {
|
|
35
|
+
console.error(err);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fg from 'fast-glob';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { ESLint } from 'eslint';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
|
|
7
|
+
interface ImportValidationResult {
|
|
8
|
+
file: string;
|
|
9
|
+
errors: Array<{
|
|
10
|
+
line: number;
|
|
11
|
+
column: number;
|
|
12
|
+
message: string;
|
|
13
|
+
ruleId: string;
|
|
14
|
+
}>;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function validateImportsInFile(filePath: string): Promise<ImportValidationResult> {
|
|
18
|
+
const eslint = new ESLint({
|
|
19
|
+
baseConfig: {
|
|
20
|
+
parser: '@typescript-eslint/parser',
|
|
21
|
+
parserOptions: {
|
|
22
|
+
ecmaVersion: 2020,
|
|
23
|
+
sourceType: 'module',
|
|
24
|
+
},
|
|
25
|
+
rules: {
|
|
26
|
+
'@futdevpro/dynamo/import-order': 'error',
|
|
27
|
+
},
|
|
28
|
+
plugins: {
|
|
29
|
+
'@futdevpro/dynamo': {
|
|
30
|
+
rules: {
|
|
31
|
+
'import-order': require('../plugin/rules/import-order').default,
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
} as any,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const results = await eslint.lintFiles([filePath]);
|
|
39
|
+
const result = results[0];
|
|
40
|
+
|
|
41
|
+
return {
|
|
42
|
+
file: filePath,
|
|
43
|
+
errors: result.messages.map(msg => ({
|
|
44
|
+
line: msg.line,
|
|
45
|
+
column: msg.column,
|
|
46
|
+
message: msg.message,
|
|
47
|
+
ruleId: msg.ruleId || 'unknown',
|
|
48
|
+
})),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function main() {
|
|
53
|
+
const files = await fg(['**/*.{ts,tsx}'], {
|
|
54
|
+
ignore: ['**/node_modules/**', '**/build/**', '**/dist/**', '**/*.spec.ts', '**/*.test.ts']
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
console.log(`[dynamo-validate-imports] Scanning ${files.length} files...`);
|
|
58
|
+
|
|
59
|
+
const results: ImportValidationResult[] = [];
|
|
60
|
+
let totalErrors = 0;
|
|
61
|
+
|
|
62
|
+
for (const file of files) {
|
|
63
|
+
try {
|
|
64
|
+
const result = await validateImportsInFile(file);
|
|
65
|
+
results.push(result);
|
|
66
|
+
totalErrors += result.errors.length;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
console.error(`Error processing ${file}:`, error);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Report results
|
|
73
|
+
const filesWithErrors = results.filter(r => r.errors.length > 0);
|
|
74
|
+
|
|
75
|
+
if (filesWithErrors.length === 0) {
|
|
76
|
+
console.log('ā
All import validations passed!');
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
console.log(`\nā Found ${totalErrors} import validation errors in ${filesWithErrors.length} files:\n`);
|
|
81
|
+
|
|
82
|
+
filesWithErrors.forEach(result => {
|
|
83
|
+
console.log(`š ${result.file}:`);
|
|
84
|
+
result.errors.forEach(error => {
|
|
85
|
+
console.log(` ${error.line}:${error.column} - ${error.message}`);
|
|
86
|
+
});
|
|
87
|
+
console.log('');
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
main().catch((err) => {
|
|
94
|
+
console.error('Validation failed:', err);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fg from 'fast-glob';
|
|
3
|
+
import { readFileSync } from 'fs';
|
|
4
|
+
import { ESLint } from 'eslint';
|
|
5
|
+
|
|
6
|
+
interface NamingValidationResult {
|
|
7
|
+
file: string;
|
|
8
|
+
errors: Array<{
|
|
9
|
+
line: number;
|
|
10
|
+
column: number;
|
|
11
|
+
message: string;
|
|
12
|
+
ruleId: string;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function validateNamingInFile(filePath: string): Promise<NamingValidationResult> {
|
|
17
|
+
const eslint = new ESLint({
|
|
18
|
+
baseConfig: {
|
|
19
|
+
parser: '@typescript-eslint/parser',
|
|
20
|
+
parserOptions: {
|
|
21
|
+
ecmaVersion: 2020,
|
|
22
|
+
sourceType: 'module',
|
|
23
|
+
},
|
|
24
|
+
rules: {
|
|
25
|
+
'@futdevpro/dynamo/naming-patterns': 'error',
|
|
26
|
+
},
|
|
27
|
+
plugins: {
|
|
28
|
+
'@futdevpro/dynamo': {
|
|
29
|
+
rules: {
|
|
30
|
+
'naming-patterns': require('../plugin/rules/naming-patterns').default,
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
} as any,
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const results = await eslint.lintFiles([filePath]);
|
|
38
|
+
const result = results[0];
|
|
39
|
+
|
|
40
|
+
return {
|
|
41
|
+
file: filePath,
|
|
42
|
+
errors: result.messages.map(msg => ({
|
|
43
|
+
line: msg.line,
|
|
44
|
+
column: msg.column,
|
|
45
|
+
message: msg.message,
|
|
46
|
+
ruleId: msg.ruleId || 'unknown',
|
|
47
|
+
})),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function main() {
|
|
52
|
+
const files = await fg(['**/*.{ts,tsx}'], {
|
|
53
|
+
ignore: ['**/node_modules/**', '**/build/**', '**/dist/**', '**/*.spec.ts', '**/*.test.ts']
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log(`[dynamo-validate-naming] Scanning ${files.length} files...`);
|
|
57
|
+
|
|
58
|
+
const results: NamingValidationResult[] = [];
|
|
59
|
+
let totalErrors = 0;
|
|
60
|
+
|
|
61
|
+
for (const file of files) {
|
|
62
|
+
try {
|
|
63
|
+
const result = await validateNamingInFile(file);
|
|
64
|
+
results.push(result);
|
|
65
|
+
totalErrors += result.errors.length;
|
|
66
|
+
} catch (error) {
|
|
67
|
+
console.error(`Error processing ${file}:`, error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Report results
|
|
72
|
+
const filesWithErrors = results.filter(r => r.errors.length > 0);
|
|
73
|
+
|
|
74
|
+
if (filesWithErrors.length === 0) {
|
|
75
|
+
console.log('ā
All naming validations passed!');
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
console.log(`\nā Found ${totalErrors} naming validation errors in ${filesWithErrors.length} files:\n`);
|
|
80
|
+
|
|
81
|
+
filesWithErrors.forEach(result => {
|
|
82
|
+
console.log(`š ${result.file}:`);
|
|
83
|
+
result.errors.forEach(error => {
|
|
84
|
+
console.log(` ${error.line}:${error.column} - ${error.message}`);
|
|
85
|
+
});
|
|
86
|
+
console.log('');
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
main().catch((err) => {
|
|
93
|
+
console.error('Validation failed:', err);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"incremental": false,
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"target": "es2022",
|
|
6
|
+
"lib": [
|
|
7
|
+
"es6",
|
|
8
|
+
"es2018",
|
|
9
|
+
"es2022",
|
|
10
|
+
"dom"
|
|
11
|
+
],
|
|
12
|
+
"strict": false,
|
|
13
|
+
"allowJs": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"sourceMap": true,
|
|
17
|
+
"outDir": "./build",
|
|
18
|
+
"rootDir": "./src",
|
|
19
|
+
"importHelpers": true,
|
|
20
|
+
"moduleResolution": "node",
|
|
21
|
+
"esModuleInterop": true,
|
|
22
|
+
"skipLibCheck": true,
|
|
23
|
+
"noImplicitOverride": true,
|
|
24
|
+
"allowSyntheticDefaultImports": true,
|
|
25
|
+
"forceConsistentCasingInFileNames": true,
|
|
26
|
+
"resolveJsonModule": true
|
|
27
|
+
},
|
|
28
|
+
"include": [
|
|
29
|
+
"src",
|
|
30
|
+
"src/**/assets/*"
|
|
31
|
+
]
|
|
32
|
+
}
|