@futdevpro/dynamo-eslint 1.15.20 → 1.15.21
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/.dynamo/pipeline.cicd.config.json +3 -3
- package/build/plugin/rules/prefer-interface-over-duplicate-types.d.ts.map +1 -1
- package/build/plugin/rules/prefer-interface-over-duplicate-types.js +74 -49
- package/build/plugin/rules/prefer-interface-over-duplicate-types.js.map +1 -1
- package/package.json +1 -1
- package/src/plugin/rules/prefer-interface-over-duplicate-types.spec.ts +37 -12
- package/src/plugin/rules/prefer-interface-over-duplicate-types.ts +56 -27
|
@@ -151,7 +151,7 @@
|
|
|
151
151
|
},
|
|
152
152
|
{
|
|
153
153
|
"label": "pipeline-report",
|
|
154
|
-
"command": "fdp pipeline-report --report ./logs/cicd-pipeline/cicd-report.json --overseer-
|
|
154
|
+
"command": "fdp pipeline-report --report ./logs/cicd-pipeline/cicd-report.json --overseer-secret $OVERSEER_SECRET_KEY --branch $GITHUB_REF_NAME",
|
|
155
155
|
"fatal": false,
|
|
156
156
|
"always": true,
|
|
157
157
|
"discord": {
|
|
@@ -160,13 +160,13 @@
|
|
|
160
160
|
},
|
|
161
161
|
"condition": {
|
|
162
162
|
"envSet": [
|
|
163
|
-
"
|
|
163
|
+
"OVERSEER_SECRET_KEY"
|
|
164
164
|
]
|
|
165
165
|
}
|
|
166
166
|
},
|
|
167
167
|
{
|
|
168
168
|
"label": "discord-result",
|
|
169
|
-
"command": "fdp pipeline-notify --report ./logs/cicd-pipeline/cicd-report.json --config ./pipeline.cicd.config.json --app-name Dynamo-ESLint --app-emoji 🔧 --webhook-id $DISCORD_WEBHOOK_ID --webhook-token $DISCORD_WEBHOOK_TOKEN
|
|
169
|
+
"command": "fdp pipeline-notify --report ./logs/cicd-pipeline/cicd-report.json --config ./pipeline.cicd.config.json --app-name Dynamo-ESLint --app-emoji 🔧 --webhook-id $DISCORD_WEBHOOK_ID --webhook-token $DISCORD_WEBHOOK_TOKEN",
|
|
170
170
|
"fatal": false,
|
|
171
171
|
"retryOnFail": 2,
|
|
172
172
|
"retryDelaySec": 5,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-interface-over-duplicate-types.d.ts","sourceRoot":"","sources":["../../../src/plugin/rules/prefer-interface-over-duplicate-types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAY9B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"prefer-interface-over-duplicate-types.d.ts","sourceRoot":"","sources":["../../../src/plugin/rules/prefer-interface-over-duplicate-types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,QAAQ,CAAC;AAY9B,QAAA,MAAM,IAAI,EAAE,IAAI,CAAC,UA2ShB,CAAC;AAEF,eAAe,IAAI,CAAC"}
|
|
@@ -22,13 +22,12 @@ const rule = {
|
|
|
22
22
|
],
|
|
23
23
|
messages: {
|
|
24
24
|
preferInterface: 'Object type used {{count}} times. Consider creating interface "{{interfaceName}}"}}',
|
|
25
|
-
extractInterface: 'Extract to interface "{{interfaceName}}" (⚠️ review the generated types — complex/union/qualified types may need manual correction).',
|
|
26
25
|
},
|
|
27
|
-
// BFR-ADVENTOR-009
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
26
|
+
// BFR-ADVENTOR-009 (2. lépcső): a fixer forrás-szöveg-megőrzőre átírva (`sourceCode.getText` per property)
|
|
27
|
+
// → a komplex/union/qualified/function típusok EXAKT megőrződnek, a generált interface ÉRVÉNYES TS. Ezért
|
|
28
|
+
// az auto-fix (`fixable:'code'`) VISSZA — a bulk `eslint --fix` biztonságosan futtathatja (nem generál
|
|
29
|
+
// többé `reference`/`complex` literál-hibát). (1. lépcső: suggestion-degrade, most feloldva.)
|
|
30
|
+
fixable: 'code',
|
|
32
31
|
},
|
|
33
32
|
create(context) {
|
|
34
33
|
const options = context.options[0] || {};
|
|
@@ -144,6 +143,40 @@ const rule = {
|
|
|
144
143
|
}
|
|
145
144
|
return name;
|
|
146
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* BFR-ADVENTOR-009 (2. lépcső): az interface-törzs a FORRÁS-SZÖVEGBŐL épül (sourceCode.getText per
|
|
148
|
+
* member), NEM a lossy normalizált signature-ből → a komplex/union/qualified/function típusok EXAKT
|
|
149
|
+
* megőrződnek (a korábbi `reference`/`complex`/`union` literál-hibák megszűnnek). A `getText` az eredeti
|
|
150
|
+
* property-szöveget adja (pl. `onError: (e: Error) => void`); a záró `;`/`,`-t normalizáljuk.
|
|
151
|
+
*/
|
|
152
|
+
function buildInterfaceBody(node) {
|
|
153
|
+
const members = Array.isArray(node?.members) ? node.members : [];
|
|
154
|
+
return members
|
|
155
|
+
.map((member) => {
|
|
156
|
+
const text = sourceCode.getText(member).trim().replace(/[;,]\s*$/, '');
|
|
157
|
+
return ` ${text};`;
|
|
158
|
+
})
|
|
159
|
+
.join('\n');
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Beszédes interface-név a property-kulcsokból (BFR-ADVENTOR-009): a nem-beszédes, ütköző
|
|
163
|
+
* `GeneratedInterfaceN` helyett az első 1-3 kulcsból PascalCase + `Params_Interface` (a projekt-konvenció).
|
|
164
|
+
*/
|
|
165
|
+
function deriveInterfaceName(node) {
|
|
166
|
+
const members = Array.isArray(node?.members) ? node.members : [];
|
|
167
|
+
const keys = members
|
|
168
|
+
.filter((m) => m.type === 'TSPropertySignature')
|
|
169
|
+
.map((m) => String(m.key?.name ?? m.key?.value ?? ''))
|
|
170
|
+
.filter((k) => !!k)
|
|
171
|
+
.slice(0, 3);
|
|
172
|
+
if (!keys.length) {
|
|
173
|
+
return 'Generated_Interface';
|
|
174
|
+
}
|
|
175
|
+
const pascal = keys
|
|
176
|
+
.map((k) => k.charAt(0).toUpperCase() + k.slice(1))
|
|
177
|
+
.join('');
|
|
178
|
+
return `${pascal}_Interface`;
|
|
179
|
+
}
|
|
147
180
|
return {
|
|
148
181
|
TSTypeLiteral(node, _context, _sourceCode) {
|
|
149
182
|
try {
|
|
@@ -169,7 +202,10 @@ const rule = {
|
|
|
169
202
|
// Process all collected type usages
|
|
170
203
|
for (const [signature, usages] of typeUsages) {
|
|
171
204
|
if (usages.length >= threshold) {
|
|
172
|
-
|
|
205
|
+
// beszédes név a property-kulcsokból (ütközés-mentesre pontosítva)
|
|
206
|
+
const interfaceName = generateUniqueInterfaceName(deriveInterfaceName(usages[0].node));
|
|
207
|
+
// a törzs a FORRÁS-SZÖVEGBŐL (getText) — a komplex/union/qualified típusok EXAKT megőrződnek
|
|
208
|
+
const interfaceBody = buildInterfaceBody(usages[0].node);
|
|
173
209
|
// Report on each usage
|
|
174
210
|
for (const usage of usages) {
|
|
175
211
|
context.report({
|
|
@@ -179,51 +215,40 @@ const rule = {
|
|
|
179
215
|
count: usages.length.toString(),
|
|
180
216
|
interfaceName,
|
|
181
217
|
},
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const firstNode = sourceCode.ast.body[0];
|
|
195
|
-
let insertPosition = 0;
|
|
196
|
-
if (firstNode && firstNode.type === 'ImportDeclaration') {
|
|
197
|
-
// Find last import
|
|
198
|
-
let lastImport = firstNode;
|
|
199
|
-
for (const node of sourceCode.ast.body) {
|
|
200
|
-
if (node.type === 'ImportDeclaration') {
|
|
201
|
-
lastImport = node;
|
|
202
|
-
}
|
|
203
|
-
else {
|
|
204
|
-
break;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
insertPosition = lastImport.range[1];
|
|
218
|
+
fix(fixer) {
|
|
219
|
+
try {
|
|
220
|
+
const interfaceDeclaration = `interface ${interfaceName} {\n${interfaceBody}\n}`;
|
|
221
|
+
// Find insertion point (after imports, before other code)
|
|
222
|
+
const firstNode = sourceCode.ast.body[0];
|
|
223
|
+
let insertPosition = 0;
|
|
224
|
+
if (firstNode && firstNode.type === 'ImportDeclaration') {
|
|
225
|
+
// Find last import
|
|
226
|
+
let lastImport = firstNode;
|
|
227
|
+
for (const node of sourceCode.ast.body) {
|
|
228
|
+
if (node.type === 'ImportDeclaration') {
|
|
229
|
+
lastImport = node;
|
|
208
230
|
}
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
fixer.insertTextAfterRange([insertPosition, insertPosition], `\n\n${interfaceDeclaration}\n`),
|
|
212
|
-
];
|
|
213
|
-
// Replace all usages with interface reference
|
|
214
|
-
for (const usage of usages) {
|
|
215
|
-
const nodeText = sourceCode.getText(usage.node);
|
|
216
|
-
const replacement = interfaceName;
|
|
217
|
-
fixes.push(fixer.replaceText(usage.node, replacement));
|
|
231
|
+
else {
|
|
232
|
+
break;
|
|
218
233
|
}
|
|
219
|
-
return fixes;
|
|
220
234
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
235
|
+
insertPosition = lastImport.range[1];
|
|
236
|
+
}
|
|
237
|
+
// Add interface declaration
|
|
238
|
+
const fixes = [
|
|
239
|
+
fixer.insertTextAfterRange([insertPosition, insertPosition], `\n\n${interfaceDeclaration}\n`),
|
|
240
|
+
];
|
|
241
|
+
// Replace all usages with interface reference
|
|
242
|
+
for (const usage of usages) {
|
|
243
|
+
fixes.push(fixer.replaceText(usage.node, interfaceName));
|
|
244
|
+
}
|
|
245
|
+
return fixes;
|
|
246
|
+
}
|
|
247
|
+
catch (fixError) {
|
|
248
|
+
console.error('[prefer-interface-over-duplicate-types] Error in fix function:', fixError);
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
},
|
|
227
252
|
});
|
|
228
253
|
}
|
|
229
254
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prefer-interface-over-duplicate-types.js","sourceRoot":"","sources":["../../../src/plugin/rules/prefer-interface-over-duplicate-types.ts"],"names":[],"mappings":";;AAYA,MAAM,IAAI,GAAoB;IAC5B,IAAI,EAAE;QACJ,IAAI,EAAE,YAAY;QAClB,IAAI,EAAE;YACJ,WAAW,EAAE,2FAA2F;YACxG,WAAW,EAAE,IAAI;SAClB;QACD,MAAM,EAAE;YACN;gBACE,IAAI,EAAE,QAAQ;gBACd,UAAU,EAAE;oBACV,SAAS,EAAE;wBACT,IAAI,EAAE,QAAQ;wBACd,OAAO,EAAE,CAAC;wBACV,OAAO,EAAE,CAAC;qBACX;iBACF;gBACD,oBAAoB,EAAE,KAAK;aAC5B;SACF;QACD,QAAQ,EAAE;YACR,eAAe,EAAE,qFAAqF;
|
|
1
|
+
{"version":3,"file":"prefer-interface-over-duplicate-types.js","sourceRoot":"","sources":["../../../src/plugin/rules/prefer-interface-over-duplicate-types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -4,10 +4,8 @@ describe('| prefer-interface-over-duplicate-types', () => {
|
|
|
4
4
|
it('| should be a valid ESLint rule', () => {
|
|
5
5
|
expect(preferInterfaceRule.meta?.type).toBe('suggestion');
|
|
6
6
|
expect(preferInterfaceRule.meta?.docs?.description).toContain('Suggest creating interfaces');
|
|
7
|
-
// BFR-ADVENTOR-009: a
|
|
8
|
-
|
|
9
|
-
expect(preferInterfaceRule.meta?.fixable).toBeUndefined();
|
|
10
|
-
expect(preferInterfaceRule.meta?.hasSuggestions).toBe(true);
|
|
7
|
+
// BFR-ADVENTOR-009 (2. lépcső): a forrás-megőrző fixer VISSZAKAPCSOLVA auto-fix-re (érvényes kódot ad).
|
|
8
|
+
expect(preferInterfaceRule.meta?.fixable).toBe('code');
|
|
11
9
|
});
|
|
12
10
|
|
|
13
11
|
it('| should have create function that returns visitor object', () => {
|
|
@@ -241,17 +239,35 @@ describe('| prefer-interface-over-duplicate-types', () => {
|
|
|
241
239
|
expect(reportCalled).toBe(true);
|
|
242
240
|
});
|
|
243
241
|
|
|
244
|
-
it('|
|
|
242
|
+
it('| auto-fix a FORRÁS-SZÖVEGBŐL épít (BFR-ADVENTOR-009 2. lépcső: komplex típus megőrizve, nem `reference`)', () => {
|
|
243
|
+
// egy komplex property-t szimulálunk: a member getText-je egy function-type — a régi lossy fixer
|
|
244
|
+
// ezt `reference`/`complex`-re redukálta (érvénytelen TS); az új fixer EXAKT megőrzi.
|
|
245
|
+
const complexMember: any = {
|
|
246
|
+
type: 'TSPropertySignature', key: { name: 'onError' },
|
|
247
|
+
};
|
|
248
|
+
const numberMember: any = {
|
|
249
|
+
type: 'TSPropertySignature', key: { name: 'count' },
|
|
250
|
+
};
|
|
251
|
+
const memberTexts: Map<any, string> = new Map([
|
|
252
|
+
[ complexMember, 'onError: (e: Error) => void' ],
|
|
253
|
+
[ numberMember, 'count: number' ],
|
|
254
|
+
]);
|
|
255
|
+
|
|
256
|
+
let insertedText: string = '';
|
|
257
|
+
const mockFixer: any = {
|
|
258
|
+
insertTextAfterRange: (_range: unknown, text: string): unknown => { insertedText = text; return {}; },
|
|
259
|
+
replaceText: (): unknown => ({}),
|
|
260
|
+
};
|
|
261
|
+
|
|
245
262
|
const mockContext = {
|
|
246
263
|
report: (options: any) => {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
expect(Array.isArray(options.suggest)).toBe(true);
|
|
250
|
-
expect(typeof options.suggest[0].fix).toBe('function');
|
|
264
|
+
expect(typeof options.fix).toBe('function'); // auto-fix VISSZA (nem suggestion)
|
|
265
|
+
options.fix(mockFixer);
|
|
251
266
|
},
|
|
252
267
|
options: [{ threshold: 2 }],
|
|
253
268
|
sourceCode: {
|
|
254
|
-
|
|
269
|
+
// a body-generátor a member-node getText-jét használja; a normalizáló getTypeString nem fut a fixerben
|
|
270
|
+
getText: (n: any): string => memberTexts.get(n) ?? '{ onError: fn; count: number }',
|
|
255
271
|
getScope: () => ({ variables: [] }),
|
|
256
272
|
ast: { body: [] },
|
|
257
273
|
},
|
|
@@ -262,14 +278,23 @@ describe('| prefer-interface-over-duplicate-types', () => {
|
|
|
262
278
|
const mockNode = {
|
|
263
279
|
type: 'TSTypeLiteral',
|
|
264
280
|
members: [
|
|
265
|
-
{
|
|
266
|
-
{
|
|
281
|
+
{ ...complexMember, typeAnnotation: { typeAnnotation: { type: 'TSFunctionType' } } },
|
|
282
|
+
{ ...numberMember, typeAnnotation: { typeAnnotation: { type: 'TSNumberKeyword' } } },
|
|
267
283
|
],
|
|
268
284
|
} as any;
|
|
285
|
+
// a getText a fenti two member-referenciákat kapja — a mockNode.members MÁSOK, ezért a getText-map
|
|
286
|
+
// kulcsait használjuk a mockNode.members-ként:
|
|
287
|
+
mockNode.members = [ complexMember, numberMember ];
|
|
269
288
|
|
|
270
289
|
rule.TSTypeLiteral(mockNode);
|
|
271
290
|
rule.TSTypeLiteral(mockNode);
|
|
272
291
|
rule['Program:exit']({} as any);
|
|
292
|
+
|
|
293
|
+
// a generált interface a MEGŐRZÖTT function-type-ot tartalmazza, NEM `reference`/`complex`-et
|
|
294
|
+
expect(insertedText).toContain('onError: (e: Error) => void;');
|
|
295
|
+
expect(insertedText).toContain('count: number;');
|
|
296
|
+
expect(insertedText).not.toContain('reference');
|
|
297
|
+
expect(insertedText).not.toContain('complex');
|
|
273
298
|
});
|
|
274
299
|
|
|
275
300
|
it('| should handle default threshold when no options provided', () => {
|
|
@@ -32,13 +32,12 @@ const rule: Rule.RuleModule = {
|
|
|
32
32
|
],
|
|
33
33
|
messages: {
|
|
34
34
|
preferInterface: 'Object type used {{count}} times. Consider creating interface "{{interfaceName}}"}}',
|
|
35
|
-
extractInterface: 'Extract to interface "{{interfaceName}}" (⚠️ review the generated types — complex/union/qualified types may need manual correction).',
|
|
36
35
|
},
|
|
37
|
-
// BFR-ADVENTOR-009
|
|
38
|
-
//
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
|
|
36
|
+
// BFR-ADVENTOR-009 (2. lépcső): a fixer forrás-szöveg-megőrzőre átírva (`sourceCode.getText` per property)
|
|
37
|
+
// → a komplex/union/qualified/function típusok EXAKT megőrződnek, a generált interface ÉRVÉNYES TS. Ezért
|
|
38
|
+
// az auto-fix (`fixable:'code'`) VISSZA — a bulk `eslint --fix` biztonságosan futtathatja (nem generál
|
|
39
|
+
// többé `reference`/`complex` literál-hibát). (1. lépcső: suggestion-degrade, most feloldva.)
|
|
40
|
+
fixable: 'code',
|
|
42
41
|
},
|
|
43
42
|
create(context) {
|
|
44
43
|
const options: RuleOptions = context.options[0] || {};
|
|
@@ -164,15 +163,55 @@ const rule: Rule.RuleModule = {
|
|
|
164
163
|
function generateUniqueInterfaceName(baseName: string): string {
|
|
165
164
|
let name = baseName;
|
|
166
165
|
let counter = 1;
|
|
167
|
-
|
|
166
|
+
|
|
168
167
|
while (isNameConflict(name)) {
|
|
169
168
|
name = `${baseName}${counter}`;
|
|
170
169
|
counter++;
|
|
171
170
|
}
|
|
172
|
-
|
|
171
|
+
|
|
173
172
|
return name;
|
|
174
173
|
}
|
|
175
174
|
|
|
175
|
+
/**
|
|
176
|
+
* BFR-ADVENTOR-009 (2. lépcső): az interface-törzs a FORRÁS-SZÖVEGBŐL épül (sourceCode.getText per
|
|
177
|
+
* member), NEM a lossy normalizált signature-ből → a komplex/union/qualified/function típusok EXAKT
|
|
178
|
+
* megőrződnek (a korábbi `reference`/`complex`/`union` literál-hibák megszűnnek). A `getText` az eredeti
|
|
179
|
+
* property-szöveget adja (pl. `onError: (e: Error) => void`); a záró `;`/`,`-t normalizáljuk.
|
|
180
|
+
*/
|
|
181
|
+
function buildInterfaceBody(node: any): string {
|
|
182
|
+
const members: any[] = Array.isArray(node?.members) ? node.members : [];
|
|
183
|
+
|
|
184
|
+
return members
|
|
185
|
+
.map((member: any): string => {
|
|
186
|
+
const text: string = sourceCode.getText(member).trim().replace(/[;,]\s*$/, '');
|
|
187
|
+
return ` ${text};`;
|
|
188
|
+
})
|
|
189
|
+
.join('\n');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Beszédes interface-név a property-kulcsokból (BFR-ADVENTOR-009): a nem-beszédes, ütköző
|
|
194
|
+
* `GeneratedInterfaceN` helyett az első 1-3 kulcsból PascalCase + `Params_Interface` (a projekt-konvenció).
|
|
195
|
+
*/
|
|
196
|
+
function deriveInterfaceName(node: any): string {
|
|
197
|
+
const members: any[] = Array.isArray(node?.members) ? node.members : [];
|
|
198
|
+
const keys: string[] = members
|
|
199
|
+
.filter((m: any): boolean => m.type === 'TSPropertySignature')
|
|
200
|
+
.map((m: any): string => String(m.key?.name ?? m.key?.value ?? ''))
|
|
201
|
+
.filter((k: string): boolean => !!k)
|
|
202
|
+
.slice(0, 3);
|
|
203
|
+
|
|
204
|
+
if (!keys.length) {
|
|
205
|
+
return 'Generated_Interface';
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const pascal: string = keys
|
|
209
|
+
.map((k: string): string => k.charAt(0).toUpperCase() + k.slice(1))
|
|
210
|
+
.join('');
|
|
211
|
+
|
|
212
|
+
return `${pascal}_Interface`;
|
|
213
|
+
}
|
|
214
|
+
|
|
176
215
|
return {
|
|
177
216
|
TSTypeLiteral(node: any, _context?: any, _sourceCode?: any): void {
|
|
178
217
|
try {
|
|
@@ -202,10 +241,14 @@ const rule: Rule.RuleModule = {
|
|
|
202
241
|
// Process all collected type usages
|
|
203
242
|
for (const [ signature, usages ] of typeUsages) {
|
|
204
243
|
if (usages.length >= threshold) {
|
|
244
|
+
// beszédes név a property-kulcsokból (ütközés-mentesre pontosítva)
|
|
205
245
|
const interfaceName = generateUniqueInterfaceName(
|
|
206
|
-
|
|
246
|
+
deriveInterfaceName(usages[0].node)
|
|
207
247
|
);
|
|
208
248
|
|
|
249
|
+
// a törzs a FORRÁS-SZÖVEGBŐL (getText) — a komplex/union/qualified típusok EXAKT megőrződnek
|
|
250
|
+
const interfaceBody: string = buildInterfaceBody(usages[0].node);
|
|
251
|
+
|
|
209
252
|
// Report on each usage
|
|
210
253
|
for (const usage of usages) {
|
|
211
254
|
context.report({
|
|
@@ -215,19 +258,9 @@ const rule: Rule.RuleModule = {
|
|
|
215
258
|
count: usages.length.toString(),
|
|
216
259
|
interfaceName,
|
|
217
260
|
},
|
|
218
|
-
|
|
219
|
-
messageId: 'extractInterface',
|
|
220
|
-
data: { interfaceName },
|
|
221
|
-
fix(fixer) {
|
|
261
|
+
fix(fixer) {
|
|
222
262
|
try {
|
|
223
|
-
|
|
224
|
-
const properties = signature.split(';').map(prop => {
|
|
225
|
-
const [ key, type ] = prop.split(':');
|
|
226
|
-
|
|
227
|
-
return ` ${key}: ${type};`;
|
|
228
|
-
}).join('\n');
|
|
229
|
-
|
|
230
|
-
const interfaceDeclaration = `interface ${interfaceName} {\n${properties}\n}`;
|
|
263
|
+
const interfaceDeclaration = `interface ${interfaceName} {\n${interfaceBody}\n}`;
|
|
231
264
|
|
|
232
265
|
// Find insertion point (after imports, before other code)
|
|
233
266
|
const firstNode = sourceCode.ast.body[0];
|
|
@@ -254,11 +287,8 @@ const rule: Rule.RuleModule = {
|
|
|
254
287
|
|
|
255
288
|
// Replace all usages with interface reference
|
|
256
289
|
for (const usage of usages) {
|
|
257
|
-
const nodeText = sourceCode.getText(usage.node);
|
|
258
|
-
const replacement = interfaceName;
|
|
259
|
-
|
|
260
290
|
fixes.push(
|
|
261
|
-
fixer.replaceText(usage.node,
|
|
291
|
+
fixer.replaceText(usage.node, interfaceName)
|
|
262
292
|
);
|
|
263
293
|
}
|
|
264
294
|
|
|
@@ -268,8 +298,7 @@ const rule: Rule.RuleModule = {
|
|
|
268
298
|
|
|
269
299
|
return null;
|
|
270
300
|
}
|
|
271
|
-
|
|
272
|
-
}],
|
|
301
|
+
},
|
|
273
302
|
});
|
|
274
303
|
}
|
|
275
304
|
}
|