Skip to content

Commit 0a454ec

Browse files
committed
refactor: Add support for sap.ui.require calls
1 parent e1bd7d4 commit 0a454ec

File tree

6 files changed

+235
-80
lines changed

6 files changed

+235
-80
lines changed

src/autofix/autofix.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {ModuleDeclaration} from "../linter/ui5Types/amdTranspiler/parseModuleDec
66
import generateSolutionNoGlobals from "./solutions/noGlobals.js";
77
import {getLogger} from "@ui5/logger";
88
import {addDependencies} from "./solutions/amdImports.js";
9+
import {RequireExpression} from "../linter/ui5Types/amdTranspiler/parseRequire.js";
910

1011
const log = getLogger("linter:autofix");
1112

@@ -53,7 +54,7 @@ interface DeleteChange extends AbstractChangeSet {
5354
export type AutofixResult = Map<ResourcePath, string>;
5455
type SourceFiles = Map<ResourcePath, ts.SourceFile>;
5556

56-
interface Position {
57+
export interface Position {
5758
line: number;
5859
column: number;
5960
pos: number;
@@ -82,7 +83,7 @@ export type ImportRequests = Map<string, {
8283
export type ModuleDeclarationInfo = ExistingModuleDeclarationInfo | NewModuleDeclarationInfo;
8384

8485
export interface ExistingModuleDeclarationInfo {
85-
moduleDeclaration: ModuleDeclaration;
86+
moduleDeclaration: ModuleDeclaration | RequireExpression;
8687
importRequests: ImportRequests;
8788
}
8889

src/autofix/solutions/amdImports.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -86,26 +86,30 @@ export function addDependencies(
8686
return;
8787
}
8888

89-
if (!ts.isFunctionLike(moduleDeclaration.factory)) {
89+
const factory = "factory" in moduleDeclaration ? moduleDeclaration.factory : moduleDeclaration.callback;
90+
91+
if (!factory || !ts.isFunctionLike(factory)) {
9092
throw new Error("Invalid factory function");
9193
}
9294

93-
const declaredIdentifiers = collectModuleIdentifiers(moduleDeclaration.factory);
95+
const moduleName = "moduleName" in moduleDeclaration ? moduleDeclaration.moduleName : undefined;
96+
97+
const declaredIdentifiers = collectModuleIdentifiers(factory);
9498

9599
const dependencies = moduleDeclaration.dependencies?.elements;
96100
const {dependencyMap, mostUsedQuoteStyle} = createDependencyInfo(dependencies, resourcePath);
97101

98-
const parameters = moduleDeclaration.factory.parameters;
99-
const parameterSyntax = getParameterSyntax(moduleDeclaration.factory);
102+
const parameters = factory.parameters;
103+
const parameterSyntax = getParameterSyntax(factory);
100104

101105
const newDependencies: {moduleName: string; identifier: string}[] = [];
102106

103107
// Prefer using the second element for separator detection as the first one might not have a line-break before it
104108
const depsSeparator = extractIdentifierSeparator(
105109
dependencies?.[1]?.getFullText() ?? dependencies?.[0]?.getFullText() ?? "");
106110
const identifiersSeparator = extractIdentifierSeparator(
107-
moduleDeclaration.factory.parameters[1]?.getFullText() ??
108-
moduleDeclaration.factory.parameters[0]?.getFullText() ?? "");
111+
factory.parameters[1]?.getFullText() ??
112+
factory.parameters[0]?.getFullText() ?? "");
109113

110114
// Calculate after which index we can add new dependencies
111115
const insertAfterIndex = Math.min(parameters.length, dependencies?.length ?? 0) - 1;
@@ -168,7 +172,7 @@ export function addDependencies(
168172
});
169173
} else {
170174
// No dependencies array found, add a new one right before the factory function
171-
const start = (moduleDeclaration.moduleName ? defineCall.arguments[1] : defineCall.arguments[0]).getStart();
175+
const start = (moduleName ? defineCall.arguments[1] : defineCall.arguments[0]).getStart();
172176
changeSet.push({
173177
action: ChangeAction.INSERT,
174178
start,

src/autofix/solutions/noGlobals.ts

Lines changed: 54 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,11 @@ import type {
77
GlobalPropertyAccessNodeInfo,
88
ModuleDeclarationInfo,
99
NewModuleDeclarationInfo,
10+
Position,
1011
} from "../autofix.js";
1112
import {findGreatestAccessExpression, matchPropertyAccessExpression} from "../utils.js";
1213
import parseModuleDeclaration from "../../linter/ui5Types/amdTranspiler/parseModuleDeclaration.js";
14+
import parseRequire from "../../linter/ui5Types/amdTranspiler/parseRequire.js";
1315
import {getLogger} from "@ui5/logger";
1416

1517
const log = getLogger("linter:autofix:NoGlobals");
@@ -47,7 +49,8 @@ export default function generateSolutionNoGlobals(
4749
});
4850
}
4951

50-
const sapUiDefineCalls: ts.CallExpression[] = [];
52+
const moduleDeclarations = new Map<ts.CallExpression, ExistingModuleDeclarationInfo>();
53+
5154
function visitNode(node: ts.Node) {
5255
for (const nodeInfo of affectedNodesInfo) {
5356
if (node.getStart() === nodeInfo.position.pos) {
@@ -62,7 +65,31 @@ export default function generateSolutionNoGlobals(
6265
if (ts.isCallExpression(node) &&
6366
ts.isPropertyAccessExpression(node.expression)) {
6467
if (matchPropertyAccessExpression(node.expression, "sap.ui.define")) {
65-
sapUiDefineCalls.push(node);
68+
try {
69+
moduleDeclarations.set(node, {
70+
moduleDeclaration: parseModuleDeclaration(node.arguments, checker),
71+
importRequests: new Map(),
72+
});
73+
} catch (err) {
74+
const errorMessage = err instanceof Error ? err.message : String(err);
75+
log.verbose(`Failed to parse sap.ui.define ` +
76+
`call in ${sourceFile.fileName}: ${errorMessage}`);
77+
}
78+
} else if (matchPropertyAccessExpression(node.expression, "sap.ui.require")) {
79+
try {
80+
const requireExpression = parseRequire(node.arguments, checker);
81+
// Only handle async require calls, not sap.ui.require probing
82+
if (requireExpression.async) {
83+
moduleDeclarations.set(node, {
84+
moduleDeclaration: parseModuleDeclaration(node.arguments, checker),
85+
importRequests: new Map(),
86+
});
87+
}
88+
} catch (err) {
89+
const errorMessage = err instanceof Error ? err.message : String(err);
90+
log.verbose(`Failed to parse sap.ui.require ` +
91+
`call in ${sourceFile.fileName}: ${errorMessage}`);
92+
}
6693
}
6794
}
6895
ts.forEachChild(node, visitNode);
@@ -74,72 +101,50 @@ export default function generateSolutionNoGlobals(
74101
}
75102
}
76103

77-
const moduleDeclarations = new Map<ts.CallExpression, ExistingModuleDeclarationInfo>();
104+
function getModuleDeclarationForPosition(position: Position): ModuleDeclarationInfo | undefined {
105+
const potentialDeclarations: {declaration: ModuleDeclarationInfo; start: number}[] = [];
106+
for (const [_, moduleDeclarationInfo] of moduleDeclarations) {
107+
const {moduleDeclaration} = moduleDeclarationInfo;
108+
const factory = "factory" in moduleDeclaration ? moduleDeclaration.factory : moduleDeclaration.callback;
109+
if (!factory || factory.getStart() > position.pos || factory.getEnd() < position.pos) {
110+
continue;
111+
}
112+
potentialDeclarations.push({
113+
declaration: moduleDeclarationInfo,
114+
start: factory.getStart(),
115+
});
116+
}
117+
// Sort by start position so that the declaration closest to the position is returned
118+
// This is relevant in case of nested sap.ui.require calls
119+
potentialDeclarations.sort((a, b) => a.start - b.start);
120+
return potentialDeclarations.pop()?.declaration;
121+
}
78122

79123
for (const nodeInfo of affectedNodesInfo) {
80124
const {moduleName, position} = nodeInfo;
81-
// Find relevant sap.ui.define call
82-
let defineCall: ts.CallExpression | undefined | null;
83-
if (sapUiDefineCalls.length === 1) {
84-
defineCall = sapUiDefineCalls[0];
85-
} else if (sapUiDefineCalls.length > 1) {
86-
for (const sapUiDefineCall of sapUiDefineCalls) {
87-
if (sapUiDefineCall.getStart() < position.pos) {
88-
defineCall = sapUiDefineCall;
89-
}
90-
}
91-
}
92-
if (defineCall === undefined) {
93-
defineCall = null;
94-
}
95-
let moduleDeclaration: ModuleDeclarationInfo | undefined;
96-
if (defineCall) {
97-
moduleDeclaration = moduleDeclarations.get(defineCall);
98-
if (!moduleDeclaration) {
99-
try {
100-
moduleDeclaration = {
101-
moduleDeclaration: parseModuleDeclaration(defineCall.arguments, checker),
102-
importRequests: new Map(),
103-
};
104-
moduleDeclarations.set(defineCall, moduleDeclaration);
105-
} catch (err) {
106-
const errorMessage = err instanceof Error ? err.message : String(err);
107-
log.verbose(`Failed to autofix ${moduleName} in sap.ui.define ` +
108-
`call in ${sourceFile.fileName}: ${errorMessage}`);
109-
}
110-
}
111-
} else {
125+
let moduleDeclarationInfo: ModuleDeclarationInfo | undefined = getModuleDeclarationForPosition(position);
126+
if (!moduleDeclarationInfo) {
112127
if (!newModuleDeclarations.length) {
113128
// throw new Error(`TODO: Implement handling for global access without module declaration`);
114129
}
115130
for (const decl of newModuleDeclarations) {
116131
if (position.pos > decl.declareCall.getStart()) {
117-
moduleDeclaration = decl;
132+
moduleDeclarationInfo = decl;
118133
} else {
119134
break;
120135
}
121136
}
122137
}
123-
if (!moduleDeclaration) {
138+
if (!moduleDeclarationInfo) {
124139
// throw new Error(`TODO: Implement handling for global access without module declaration`);
125140
}
126141

127-
// Skip nodes outside of the module declaration factory
128-
if (moduleDeclaration && "moduleDeclaration" in moduleDeclaration) {
129-
const factory = moduleDeclaration.moduleDeclaration.factory;
130-
if (factory.getStart() > position.pos || factory.getEnd() < position.pos) {
131-
log.silly(`Skipping global access ${nodeInfo.globalVariableName} ` +
132-
`outside of module declaration factory`);
133-
continue;
134-
}
135-
}
136-
137-
if (moduleDeclaration && !moduleDeclaration.importRequests.has(moduleName)) {
138-
moduleDeclaration.importRequests.set(moduleName, {
142+
if (moduleDeclarationInfo && !moduleDeclarationInfo.importRequests.has(moduleName)) {
143+
moduleDeclarationInfo.importRequests.set(moduleName, {
139144
nodeInfos: [],
140145
});
141146
}
142-
moduleDeclaration?.importRequests.get(moduleName)!.nodeInfos.push(nodeInfo);
147+
moduleDeclarationInfo?.importRequests.get(moduleName)!.nodeInfos.push(nodeInfo);
143148
}
144149

145150
return moduleDeclarations;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
sap.ui.require([
2+
"sap/m/Button",
3+
"sap/m/Avatar",
4+
'sap/m/ComboBox',
5+
], function(
6+
ButtonRenamed,
7+
Avatar
8+
) {
9+
const avatarDOM = jQuery("#container-todo---app--avatar-profile");
10+
const list = sap.ui.getCore().byId("container-todo---app--todoList");
11+
sap.m.BackgroundDesign.Solid
12+
const button = new sap.m.Button({
13+
text: "Hello"
14+
});
15+
const button2 = new ButtonRenamed({
16+
text: "Hello"
17+
});
18+
const button3 = new window.sap.m.Button({
19+
text: "Hello"
20+
});
21+
const fileUploader = new sap.ui.unified.FileUploader({
22+
valueState: sap.ui.core.ValueState.Success
23+
});
24+
const core = sap.ui.core;
25+
const fileUploader2 = new sap.ui.unified.FileUploader({
26+
valueState: core.ValueState.Success
27+
});
28+
sap.ui.view("myView");
29+
sap.m.URLHelper.triggerSms();
30+
31+
const avatar = new sap.m.Avatar();
32+
33+
sap.ui.require(["sap/m/Dialog", "sap/m/MessageToast", "sap/f/library"], function(Dialog, MessageToast, fLib) {
34+
sap.f.AvatarType.Icon;
35+
fLib.AvatarType.Image;
36+
});
37+
});

0 commit comments

Comments
 (0)