@@ -5,10 +5,12 @@ import {MESSAGE} from "../linter/messages.js";
5
5
import { ModuleDeclaration } from "../linter/ui5Types/amdTranspiler/parseModuleDeclaration.js" ;
6
6
import generateSolutionNoGlobals from "./solutions/noGlobals.js" ;
7
7
import { getLogger } from "@ui5/logger" ;
8
- import { addDependencies } from "./solutions/amdImports.js" ;
8
+ import { addDependencies , removeDependencies } from "./solutions/amdImports.js" ;
9
9
import { RequireExpression } from "../linter/ui5Types/amdTranspiler/parseRequire.js" ;
10
10
import { Resource } from "@ui5/fs" ;
11
11
import { collectIdentifiers } from "./utils.js" ;
12
+ import { ExportCodeToBeUsed } from "../linter/ui5Types/fixHints/FixHints.js" ;
13
+ import generateSolutionCodeReplacer from "./solutions/codeReplacer.js" ;
12
14
13
15
const log = getLogger ( "linter:autofix" ) ;
14
16
@@ -42,7 +44,7 @@ interface InsertChange extends AbstractChangeSet {
42
44
value : string ;
43
45
}
44
46
45
- interface ReplaceChange extends AbstractChangeSet {
47
+ export interface ReplaceChange extends AbstractChangeSet {
46
48
action : ChangeAction . REPLACE ;
47
49
end : number ;
48
50
value : string ;
@@ -62,10 +64,9 @@ export interface Position {
62
64
pos : number ;
63
65
}
64
66
export interface GlobalPropertyAccessNodeInfo {
65
- globalVariableName : string ;
66
- namespace : string ;
67
67
moduleName : string ;
68
- exportName ?: string ;
68
+ exportNameToBeUsed ?: string ;
69
+ exportCodeToBeUsed ?: ExportCodeToBeUsed ;
69
70
propertyAccess ?: string ;
70
71
position : Position ;
71
72
node ?: ts . Identifier | ts . PropertyAccessExpression | ts . ElementAccessExpression ;
@@ -87,12 +88,14 @@ export type ModuleDeclarationInfo = ExistingModuleDeclarationInfo | NewModuleDec
87
88
export interface ExistingModuleDeclarationInfo {
88
89
moduleDeclaration : ModuleDeclaration | RequireExpression ;
89
90
importRequests : ImportRequests ;
91
+ additionalNodeInfos : ( DeprecatedApiAccessNode | GlobalPropertyAccessNodeInfo ) [ ] ;
90
92
}
91
93
92
94
export interface NewModuleDeclarationInfo {
93
95
declareCall : ts . CallExpression ;
94
96
requireCalls : Map < string , ts . CallExpression [ ] > ;
95
97
importRequests : ImportRequests ;
98
+ additionalNodeInfos : ( DeprecatedApiAccessNode | GlobalPropertyAccessNodeInfo ) [ ] ;
96
99
endPos ?: number ;
97
100
}
98
101
@@ -131,24 +134,31 @@ function createProgram(inputFileNames: string[], host: ts.CompilerHost): ts.Prog
131
134
return ts . createProgram ( inputFileNames , compilerOptions , host ) ;
132
135
}
133
136
134
- function getJsErrors ( code : string , resourcePath : string ) {
135
- const sourceFile = ts . createSourceFile (
137
+ function createSourceFile ( resourcePath : string , code : string ) {
138
+ return ts . createSourceFile (
136
139
resourcePath ,
137
140
code ,
138
- ts . ScriptTarget . ES2022 ,
139
- true ,
140
- ts . ScriptKind . JS
141
+ {
142
+ languageVersion : ts . ScriptTarget . ES2022 ,
143
+ jsDocParsingMode : ts . JSDocParsingMode . ParseNone ,
144
+ }
141
145
) ;
146
+ }
142
147
143
- const host = createCompilerHost ( new Map ( [ [ resourcePath , sourceFile ] ] ) ) ;
144
- const program = createProgram ( [ resourcePath ] , host ) ;
148
+ function getJsErrorsForSourceFile ( sourceFile : ts . SourceFile , program : ts . Program ) {
145
149
const diagnostics = ts . getPreEmitDiagnostics ( program , sourceFile ) ;
146
-
147
150
return diagnostics . filter ( function ( d ) {
148
151
return d . file === sourceFile && d . category === ts . DiagnosticCategory . Error ;
149
152
} ) ;
150
153
}
151
154
155
+ function getJsErrors ( code : string , resourcePath : string ) {
156
+ const sourceFile = createSourceFile ( resourcePath , code ) ;
157
+ const host = createCompilerHost ( new Map ( [ [ resourcePath , sourceFile ] ] ) ) ;
158
+ const program = createProgram ( [ resourcePath ] , host ) ;
159
+ return getJsErrorsForSourceFile ( sourceFile , program ) ;
160
+ }
161
+
152
162
function getAutofixMessages ( resource : AutofixResource ) {
153
163
// Collect modules of which at least one message has the conditional fixHint flag set
154
164
const conditionalModuleAccess = new Set < string > ( ) ;
@@ -176,6 +186,27 @@ function getAutofixMessages(resource: AutofixResource) {
176
186
return messagesById ;
177
187
}
178
188
189
+ export function getModuleDeclarationForPosition (
190
+ position : number , moduleDeclarations : Map < ts . CallExpression , ExistingModuleDeclarationInfo >
191
+ ) : ModuleDeclarationInfo | undefined {
192
+ const potentialDeclarations : { declaration : ModuleDeclarationInfo ; start : number } [ ] = [ ] ;
193
+ for ( const [ _ , moduleDeclarationInfo ] of moduleDeclarations ) {
194
+ const { moduleDeclaration} = moduleDeclarationInfo ;
195
+ const factory = "factory" in moduleDeclaration ? moduleDeclaration . factory : moduleDeclaration . callback ;
196
+ if ( ! factory || factory . getStart ( ) > position || factory . getEnd ( ) < position ) {
197
+ continue ;
198
+ }
199
+ potentialDeclarations . push ( {
200
+ declaration : moduleDeclarationInfo ,
201
+ start : factory . getStart ( ) ,
202
+ } ) ;
203
+ }
204
+ // Sort by start position so that the declaration closest to the position is returned
205
+ // This is relevant in case of nested sap.ui.require calls
206
+ potentialDeclarations . sort ( ( a , b ) => a . start - b . start ) ;
207
+ return potentialDeclarations . pop ( ) ?. declaration ;
208
+ }
209
+
179
210
export default async function ( {
180
211
rootDir : _unused1 ,
181
212
namespace : _unused2 ,
@@ -189,7 +220,9 @@ export default async function ({
189
220
const messagesById = getAutofixMessages ( autofixResource ) ;
190
221
// Currently only global access autofixes are supported
191
222
// This needs to stay aligned with the applyFixes function
192
- if ( messagesById . has ( MESSAGE . NO_GLOBALS ) ) {
223
+ if ( messagesById . has ( MESSAGE . NO_GLOBALS ) ||
224
+ messagesById . has ( MESSAGE . DEPRECATED_API_ACCESS ) ||
225
+ messagesById . has ( MESSAGE . DEPRECATED_FUNCTION_CALL ) ) {
193
226
messages . set ( autofixResource . resource . getPath ( ) , messagesById ) ;
194
227
resources . push ( autofixResource . resource ) ;
195
228
}
@@ -217,16 +250,35 @@ export default async function ({
217
250
const checker = program . getTypeChecker ( ) ;
218
251
const res : AutofixResult = new Map ( ) ;
219
252
for ( const [ resourcePath , sourceFile ] of sourceFiles ) {
253
+ // Checking for syntax errors in the original source file.
254
+ // We should not apply autofixes to files with syntax errors
255
+ const existingJsErrors = getJsErrorsForSourceFile ( sourceFile , program ) ;
256
+ if ( existingJsErrors . length ) {
257
+ log . verbose ( `Skipping autofix for '${ resourcePath } '. Syntax error in original source file : ` +
258
+ `${ existingJsErrors . map ( ( d ) => d . messageText as string ) . join ( ", " ) } ` ) ;
259
+ continue ;
260
+ }
261
+
220
262
log . verbose ( `Applying autofixes to ${ resourcePath } ` ) ;
221
- const newContent = applyFixes ( checker , sourceFile , resourcePath , messages . get ( resourcePath ) ! ) ;
263
+ let newContent ;
264
+ try {
265
+ newContent = applyFixes ( checker , sourceFile , resourcePath , messages . get ( resourcePath ) ! ) ;
266
+ } catch ( err ) {
267
+ if ( err instanceof Error ) {
268
+ log . verbose ( `Error while applying autofix to ${ resourcePath } : ${ err } ` ) ;
269
+ context . addLintingMessage ( resourcePath , MESSAGE . AUTOFIX_ERROR , { message : err . message } ) ;
270
+ continue ;
271
+ }
272
+ throw err ;
273
+ }
222
274
if ( newContent ) {
223
275
const jsErrors = getJsErrors ( newContent , resourcePath ) ;
224
276
if ( jsErrors . length ) {
225
277
const message = `Syntax error after applying autofix for '${ resourcePath } ': ` +
226
278
jsErrors . map ( ( d ) => d . messageText as string ) . join ( ", " ) ;
227
279
log . verbose ( message ) ;
228
280
log . verbose ( resourcePath + ":\n" + newContent ) ;
229
- context . addLintingMessage ( resourcePath , MESSAGE . PARSING_ERROR , { message} ) ;
281
+ context . addLintingMessage ( resourcePath , MESSAGE . AUTOFIX_ERROR , { message} ) ;
230
282
} else {
231
283
log . verbose ( `Autofix applied to ${ resourcePath } ` ) ;
232
284
res . set ( resourcePath , newContent ) ;
@@ -245,20 +297,55 @@ function applyFixes(
245
297
246
298
const changeSet : ChangeSet [ ] = [ ] ;
247
299
let existingModuleDeclarations = new Map < ts . CallExpression , ExistingModuleDeclarationInfo > ( ) ;
300
+ const messages : RawLintMessage <
301
+ MESSAGE . NO_GLOBALS | MESSAGE . DEPRECATED_API_ACCESS | MESSAGE . DEPRECATED_FUNCTION_CALL > [ ] = [ ] ;
302
+
248
303
if ( messagesById . has ( MESSAGE . NO_GLOBALS ) ) {
249
- existingModuleDeclarations = generateSolutionNoGlobals (
250
- checker , sourceFile , content ,
251
- messagesById . get ( MESSAGE . NO_GLOBALS ) as RawLintMessage < MESSAGE . NO_GLOBALS > [ ] ,
252
- changeSet , [ ] ) ;
304
+ messages . push (
305
+ ...messagesById . get ( MESSAGE . NO_GLOBALS ) as RawLintMessage < MESSAGE . NO_GLOBALS > [ ]
306
+ ) ;
307
+ }
308
+
309
+ if ( messagesById . has ( MESSAGE . DEPRECATED_API_ACCESS ) ) {
310
+ messages . push (
311
+ ...messagesById . get ( MESSAGE . DEPRECATED_API_ACCESS ) as RawLintMessage < MESSAGE . DEPRECATED_API_ACCESS > [ ]
312
+ ) ;
313
+ }
314
+
315
+ if ( messagesById . has ( MESSAGE . DEPRECATED_FUNCTION_CALL ) ) {
316
+ messages . push (
317
+ ...messagesById . get ( MESSAGE . DEPRECATED_FUNCTION_CALL ) as RawLintMessage < MESSAGE . DEPRECATED_FUNCTION_CALL > [ ]
318
+ ) ;
319
+ }
320
+
321
+ if ( messages . length === 0 ) {
322
+ return undefined ;
253
323
}
254
324
325
+ existingModuleDeclarations = generateSolutionNoGlobals (
326
+ checker , sourceFile , content ,
327
+ messages ,
328
+ changeSet , [ ] ) ;
329
+
255
330
// Collect all identifiers in the source file to ensure unique names when adding imports
256
331
const identifiers = collectIdentifiers ( sourceFile ) ;
257
332
258
333
for ( const [ defineCall , moduleDeclarationInfo ] of existingModuleDeclarations ) {
259
- addDependencies ( defineCall , moduleDeclarationInfo , changeSet , resourcePath , identifiers ) ;
334
+ // TODO: Find a better way to define modules for removal
335
+ const moduleRemovals = new Set ( [ "sap/base/strings/NormalizePolyfill" , "jquery.sap.unicode" ] ) ;
336
+
337
+ // Remove dependencies from the existing module declaration
338
+ removeDependencies ( moduleRemovals ,
339
+ moduleDeclarationInfo , changeSet , resourcePath , identifiers ) ;
340
+
341
+ // Resolve dependencies for the module declaration
342
+ addDependencies ( defineCall , moduleDeclarationInfo , changeSet , resourcePath , identifiers , moduleRemovals ) ;
260
343
}
261
344
345
+ // More complex code replacers. Mainly arguments shifting and repositioning, replacements,
346
+ // based on arguments' context
347
+ generateSolutionCodeReplacer ( existingModuleDeclarations , messages , changeSet , sourceFile , identifiers ) ;
348
+
262
349
if ( changeSet . length === 0 ) {
263
350
// No modifications needed
264
351
return undefined ;
0 commit comments