Skip to content

Commit 62b4846

Browse files
committed
refactor: Extract amd import autofix to separate file
1 parent a4ad5e2 commit 62b4846

File tree

2 files changed

+167
-161
lines changed

2 files changed

+167
-161
lines changed

src/autofix/autofix.ts

Lines changed: 2 additions & 161 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,8 @@ import LinterContext, {RawLintMessage, ResourcePath} from "../linter/LinterConte
44
import {MESSAGE} from "../linter/messages.js";
55
import {ModuleDeclaration} from "../linter/ui5Types/amdTranspiler/parseModuleDeclaration.js";
66
import generateSolutionNoGlobals from "./solutions/noGlobals.js";
7-
import {collectModuleIdentifiers} from "./utils.js";
87
import {getLogger} from "@ui5/logger";
9-
import {resolveUniqueName} from "../linter/ui5Types/utils/utils.js";
8+
import {addDependencies} from "./solutions/amdImports.js";
109

1110
const log = getLogger("linter:autofix");
1211

@@ -75,7 +74,7 @@ export interface DeprecatedApiAccessNode {
7574
node?: ts.CallExpression | ts.Identifier | ts.PropertyAccessExpression | ts.ElementAccessExpression;
7675
}
7776

78-
type ImportRequests = Map<string, {
77+
export type ImportRequests = Map<string, {
7978
nodeInfos: (DeprecatedApiAccessNode | GlobalPropertyAccessNodeInfo)[];
8079
identifier?: string;
8180
}>;
@@ -228,164 +227,6 @@ function applyFixes(
228227
return applyChanges(content, changeSet);
229228
}
230229

231-
function addDependencies(
232-
defineCall: ts.CallExpression, moduleDeclarationInfo: ExistingModuleDeclarationInfo,
233-
changeSet: ChangeSet[]
234-
) {
235-
const {moduleDeclaration, importRequests} = moduleDeclarationInfo;
236-
237-
if (importRequests.size === 0) {
238-
return;
239-
}
240-
241-
const declaredIdentifiers = collectModuleIdentifiers(moduleDeclaration.factory);
242-
243-
const defineCallArgs = defineCall.arguments;
244-
const existingImportModules = defineCall.arguments && ts.isArrayLiteralExpression(defineCallArgs[0]) ?
245-
defineCallArgs[0].elements.map((el) => ts.isStringLiteralLike(el) ? el.text : "") :
246-
[];
247-
248-
if (!ts.isFunctionLike(moduleDeclaration.factory)) {
249-
throw new Error("Invalid factory function");
250-
}
251-
const existingIdentifiers = moduleDeclaration.factory
252-
.parameters.map((param: ts.ParameterDeclaration) => (param.name as ts.Identifier).text);
253-
const existingIdentifiersLength = existingIdentifiers.length;
254-
255-
const imports = [...importRequests.keys()];
256-
257-
const identifiersForExistingImports: string[] = [];
258-
let existingIdentifiersCut = 0;
259-
existingImportModules.forEach((existingModule, index) => {
260-
const indexOf = imports.indexOf(existingModule);
261-
const identifierName = existingIdentifiers[index] ||
262-
resolveUniqueName(existingModule, declaredIdentifiers);
263-
declaredIdentifiers.add(identifierName);
264-
identifiersForExistingImports.push(identifierName);
265-
if (indexOf !== -1) {
266-
// If there are defined dependencies, but identifiers for them are missing,
267-
// and those identifiers are needed in the code, then we need to find out
268-
// up to which index we need to build identifiers and cut the rest.
269-
existingIdentifiersCut = index > existingIdentifiersCut ? (index + 1) : existingIdentifiersCut;
270-
imports.splice(indexOf, 1);
271-
importRequests.get(existingModule)!.identifier = identifierName;
272-
}
273-
});
274-
275-
// Cut identifiers that are already there
276-
identifiersForExistingImports.splice(existingIdentifiersCut);
277-
278-
const dependencies = imports.map((i) => `"${i}"`);
279-
const identifiers = [
280-
...identifiersForExistingImports,
281-
...imports.map((i) => {
282-
const identifier = resolveUniqueName(i, declaredIdentifiers);
283-
declaredIdentifiers.add(identifier);
284-
importRequests.get(i)!.identifier = identifier;
285-
return identifier;
286-
})];
287-
288-
if (dependencies.length) {
289-
// Add dependencies
290-
if (moduleDeclaration.dependencies) {
291-
const depsNode = defineCall.arguments[0];
292-
const depElementNodes = depsNode && ts.isArrayLiteralExpression(depsNode) ? depsNode.elements : [];
293-
const insertAfterElement = depElementNodes[existingIdentifiersLength - 1] ??
294-
depElementNodes[depElementNodes.length - 1];
295-
296-
if (insertAfterElement) {
297-
changeSet.push({
298-
action: ChangeAction.INSERT,
299-
start: insertAfterElement.getEnd(),
300-
value: (existingImportModules.length ? ", " : "") + dependencies.join(", "),
301-
});
302-
} else {
303-
changeSet.push({
304-
action: ChangeAction.REPLACE,
305-
start: depsNode.getFullStart(),
306-
end: depsNode.getEnd(),
307-
value: `[${dependencies.join(", ")}]`,
308-
});
309-
}
310-
} else {
311-
changeSet.push({
312-
action: ChangeAction.INSERT,
313-
start: defineCall.arguments[0].getFullStart(),
314-
value: `[${dependencies.join(", ")}], `,
315-
});
316-
}
317-
}
318-
319-
if (identifiers.length) {
320-
const closeParenToken = moduleDeclaration.factory.getChildren()
321-
.find((c) => c.kind === ts.SyntaxKind.CloseParenToken);
322-
// Factory arguments
323-
const syntaxList = moduleDeclaration.factory.getChildren()
324-
.find((c) => c.kind === ts.SyntaxKind.SyntaxList);
325-
if (!syntaxList) {
326-
throw new Error("Invalid factory syntax");
327-
}
328-
329-
// Patch factory arguments
330-
const value = (existingIdentifiersLength ? ", " : "") + identifiers.join(", ");
331-
if (!closeParenToken) {
332-
changeSet.push({
333-
action: ChangeAction.INSERT,
334-
start: syntaxList.getStart(),
335-
value: "(",
336-
});
337-
changeSet.push({
338-
action: ChangeAction.INSERT,
339-
start: syntaxList.getEnd(),
340-
value: `${value})`,
341-
});
342-
} else {
343-
let start = syntaxList.getEnd();
344-
345-
// Existing trailing comma: Insert new args before it, to keep the trailing comma
346-
const lastSyntaxListChild = syntaxList.getChildAt(syntaxList.getChildCount() - 1);
347-
if (lastSyntaxListChild?.kind === ts.SyntaxKind.CommaToken) {
348-
start = lastSyntaxListChild.getStart();
349-
}
350-
351-
changeSet.push({
352-
action: ChangeAction.INSERT,
353-
start,
354-
value,
355-
});
356-
}
357-
}
358-
359-
// Patch identifiers
360-
patchIdentifiers(importRequests, changeSet);
361-
}
362-
363-
function patchIdentifiers(importRequests: ImportRequests, changeSet: ChangeSet[]) {
364-
for (const {nodeInfos, identifier} of importRequests.values()) {
365-
if (!identifier) {
366-
throw new Error("No identifier found for import");
367-
}
368-
369-
for (const nodeInfo of nodeInfos) {
370-
let node: ts.Node = nodeInfo.node!;
371-
372-
if ("namespace" in nodeInfo && nodeInfo.namespace === "sap.ui.getCore") {
373-
node = node.parent;
374-
}
375-
const nodeStart = node.getStart();
376-
const nodeEnd = node.getEnd();
377-
const nodeReplacement = `${identifier}`;
378-
379-
changeSet.push({
380-
action: ChangeAction.REPLACE,
381-
start: nodeStart,
382-
end: nodeEnd,
383-
value: nodeReplacement,
384-
});
385-
}
386-
}
387-
}
388-
389230
function applyChanges(content: string, changeSet: ChangeSet[]): string {
390231
changeSet.sort((a, b) => a.start - b.start);
391232
const s = new MagicString(content);
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
import ts from "typescript";
2+
import {ChangeAction, ImportRequests, ChangeSet, ExistingModuleDeclarationInfo} from "../autofix.js";
3+
import {collectModuleIdentifiers} from "../utils.js";
4+
import {resolveUniqueName} from "../../linter/ui5Types/utils/utils.js";
5+
6+
export function addDependencies(
7+
defineCall: ts.CallExpression, moduleDeclarationInfo: ExistingModuleDeclarationInfo,
8+
changeSet: ChangeSet[]
9+
) {
10+
const {moduleDeclaration, importRequests} = moduleDeclarationInfo;
11+
12+
if (importRequests.size === 0) {
13+
return;
14+
}
15+
16+
const declaredIdentifiers = collectModuleIdentifiers(moduleDeclaration.factory);
17+
18+
const defineCallArgs = defineCall.arguments;
19+
const existingImportModules = defineCall.arguments && ts.isArrayLiteralExpression(defineCallArgs[0]) ?
20+
defineCallArgs[0].elements.map((el) => ts.isStringLiteralLike(el) ? el.text : "") :
21+
[];
22+
23+
if (!ts.isFunctionLike(moduleDeclaration.factory)) {
24+
throw new Error("Invalid factory function");
25+
}
26+
const existingIdentifiers = moduleDeclaration.factory
27+
.parameters.map((param: ts.ParameterDeclaration) => (param.name as ts.Identifier).text);
28+
const existingIdentifiersLength = existingIdentifiers.length;
29+
30+
const imports = [...importRequests.keys()];
31+
32+
const identifiersForExistingImports: string[] = [];
33+
let existingIdentifiersCut = 0;
34+
existingImportModules.forEach((existingModule, index) => {
35+
const indexOf = imports.indexOf(existingModule);
36+
const identifierName = existingIdentifiers[index] ||
37+
resolveUniqueName(existingModule, declaredIdentifiers);
38+
declaredIdentifiers.add(identifierName);
39+
identifiersForExistingImports.push(identifierName);
40+
if (indexOf !== -1) {
41+
// If there are defined dependencies, but identifiers for them are missing,
42+
// and those identifiers are needed in the code, then we need to find out
43+
// up to which index we need to build identifiers and cut the rest.
44+
existingIdentifiersCut = index > existingIdentifiersCut ? (index + 1) : existingIdentifiersCut;
45+
imports.splice(indexOf, 1);
46+
importRequests.get(existingModule)!.identifier = identifierName;
47+
}
48+
});
49+
50+
// Cut identifiers that are already there
51+
identifiersForExistingImports.splice(existingIdentifiersCut);
52+
53+
const dependencies = imports.map((i) => `"${i}"`);
54+
const identifiers = [
55+
...identifiersForExistingImports,
56+
...imports.map((i) => {
57+
const identifier = resolveUniqueName(i, declaredIdentifiers);
58+
declaredIdentifiers.add(identifier);
59+
importRequests.get(i)!.identifier = identifier;
60+
return identifier;
61+
})];
62+
63+
if (dependencies.length) {
64+
// Add dependencies
65+
if (moduleDeclaration.dependencies) {
66+
const depsNode = defineCall.arguments[0];
67+
const depElementNodes = depsNode && ts.isArrayLiteralExpression(depsNode) ? depsNode.elements : [];
68+
const insertAfterElement = depElementNodes[existingIdentifiersLength - 1] ??
69+
depElementNodes[depElementNodes.length - 1];
70+
71+
if (insertAfterElement) {
72+
changeSet.push({
73+
action: ChangeAction.INSERT,
74+
start: insertAfterElement.getEnd(),
75+
value: (existingImportModules.length ? ", " : "") + dependencies.join(", "),
76+
});
77+
} else {
78+
changeSet.push({
79+
action: ChangeAction.REPLACE,
80+
start: depsNode.getFullStart(),
81+
end: depsNode.getEnd(),
82+
value: `[${dependencies.join(", ")}]`,
83+
});
84+
}
85+
} else {
86+
changeSet.push({
87+
action: ChangeAction.INSERT,
88+
start: defineCall.arguments[0].getFullStart(),
89+
value: `[${dependencies.join(", ")}], `,
90+
});
91+
}
92+
}
93+
94+
if (identifiers.length) {
95+
const closeParenToken = moduleDeclaration.factory.getChildren()
96+
.find((c) => c.kind === ts.SyntaxKind.CloseParenToken);
97+
// Factory arguments
98+
const syntaxList = moduleDeclaration.factory.getChildren()
99+
.find((c) => c.kind === ts.SyntaxKind.SyntaxList);
100+
if (!syntaxList) {
101+
throw new Error("Invalid factory syntax");
102+
}
103+
104+
// Patch factory arguments
105+
const value = (existingIdentifiersLength ? ", " : "") + identifiers.join(", ");
106+
if (!closeParenToken) {
107+
changeSet.push({
108+
action: ChangeAction.INSERT,
109+
start: syntaxList.getStart(),
110+
value: "(",
111+
});
112+
changeSet.push({
113+
action: ChangeAction.INSERT,
114+
start: syntaxList.getEnd(),
115+
value: `${value})`,
116+
});
117+
} else {
118+
let start = syntaxList.getEnd();
119+
120+
// Existing trailing comma: Insert new args before it, to keep the trailing comma
121+
const lastSyntaxListChild = syntaxList.getChildAt(syntaxList.getChildCount() - 1);
122+
if (lastSyntaxListChild?.kind === ts.SyntaxKind.CommaToken) {
123+
start = lastSyntaxListChild.getStart();
124+
}
125+
126+
changeSet.push({
127+
action: ChangeAction.INSERT,
128+
start,
129+
value,
130+
});
131+
}
132+
}
133+
134+
// Patch identifiers
135+
patchIdentifiers(importRequests, changeSet);
136+
}
137+
138+
function patchIdentifiers(importRequests: ImportRequests, changeSet: ChangeSet[]) {
139+
for (const {nodeInfos, identifier} of importRequests.values()) {
140+
if (!identifier) {
141+
throw new Error("No identifier found for import");
142+
}
143+
144+
for (const nodeInfo of nodeInfos) {
145+
if (!nodeInfo.node) {
146+
continue;
147+
}
148+
let node: ts.Node = nodeInfo.node;
149+
150+
if ("namespace" in nodeInfo && nodeInfo.namespace === "sap.ui.getCore") {
151+
node = node.parent;
152+
}
153+
const nodeStart = node.getStart();
154+
const nodeEnd = node.getEnd();
155+
const nodeReplacement = `${identifier}`;
156+
157+
changeSet.push({
158+
action: ChangeAction.REPLACE,
159+
start: nodeStart,
160+
end: nodeEnd,
161+
value: nodeReplacement,
162+
});
163+
}
164+
}
165+
}

0 commit comments

Comments
 (0)