Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 4 additions & 13 deletions src/linter/ui5Types/fix/AccessExpressionFix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,13 @@ export default class AccessExpressionFix extends AccessExpressionBaseFix {
if (this.startPos === undefined || this.endPos === undefined) {
throw new Error("Start and end position are not defined");
}
if (this.params.moduleName && !this.moduleIdentifierName) {
// The identifier for the requested module has not been set
// This can happen for example if the position of the autofix is not inside
// a module definition or require block. Therefore the required dependency can not be added
// and the fix can not be applied.
return;
}
if (this.params.globalName && !this.globalIdentifierName) {
// This should not happen
throw new Error("Global identifier has not been provided");
}
let value = this.globalIdentifierName ?? this.moduleIdentifierName;
if (!value) {

const identifier = this.getIdentifiersForSingleRequest(this.params.moduleName, this.params.globalName);
if (!identifier) {
return;
}

let value = identifier;
if (this.params.propertyAccess) {
// If a property is defined, we need to access it on the identifier
value = `${value}.${this.params.propertyAccess}`;
Expand Down
89 changes: 78 additions & 11 deletions src/linter/ui5Types/fix/AccessExpressionGeneratorFix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,34 @@ import {ChangeAction} from "../../../autofix/autofix.js";
import AccessExpressionBaseFix, {AccessExpressionBaseFixParams} from "./AccessExpressionBaseFix.js";

export interface AccessExpressionGeneratorFixParams extends AccessExpressionBaseFixParams {
/**
* Modules to import. If this parameter is specified, the standalone parameters "moduleName" and
* "preferredIdentifier" must not be provided.
*/
moduleImports?: {
moduleName: string;
preferredIdentifier?: string;
}[];

/**
* Names of a global variable to use in the fix (e.g. "document"). If this parameter is specified, parameter
* "globalName" must not be provided.
*
* The fix will be provided with the identifier names or property access strings to use via
* the setIdentifierForGlobal method.
*
* For example, if there is already a conflicting identifier within the same file,
* the fix will be provided with an alternative like "globalThis.document"
*/
globalNames?: string[];

/**
* The generator function will be used to determine the value of the replacement, affecting
* the whole access expression
*
* If the return value is undefined, no change will be generated
*/
generator: (identifierName: string | undefined) => string | undefined;
generator: (identifierNames: string[]) => string | undefined;
}

/**
Expand All @@ -21,24 +41,71 @@ export default class AccessExpressionGeneratorFix extends AccessExpressionBaseFi
super(params);
}

getNewModuleDependencies() {
if (this.params.moduleName && this.params.moduleImports) {
throw new Error(
"Parameters 'moduleName' and 'moduleImports' are both defined. Only one may be used at a time.");
} else if (this.params.moduleName) {
return super.getNewModuleDependencies();
} else if (!this.params.moduleImports) {
return;
}

const usagePosition = this.startPos;
if (usagePosition === undefined) {
throw new Error("Start position is not defined");
}
return this.params.moduleImports.map((moduleImport) => {
return {
...moduleImport,
usagePosition,
};
});
}

getNewGlobalAccess() {
if (this.params.globalName && this.params.globalNames) {
throw new Error(
"Parameters 'globalName' and 'globalNames' are both defined. Only one may be used at a time.");
} else if (this.params.globalName) {
return super.getNewGlobalAccess();
} else if (!this.params.globalNames) {
return;
}
const usagePosition = this.startPos;
if (usagePosition === undefined) {
throw new Error("Start position is not defined");
}

return this.params.globalNames.map((globalName) => {
return {
globalName,
usagePosition,
};
});
}

generateChanges() {
if (this.startPos === undefined || this.endPos === undefined) {
throw new Error("Start and end position are not defined");
}
if (this.params.moduleName && !this.moduleIdentifierName) {
// The identifier for the requested module has not been set
// This can happen for example if the position of the autofix is not inside
// a module definition or require block. Therefore the required dependency can not be added
// and the fix can not be applied.
return;

let moduleNames: string[] | undefined;
if (this.params.moduleName) {
moduleNames = [this.params.moduleName];
} else if (this.params.moduleImports) {
moduleNames = this.params.moduleImports.map((moduleImport) => moduleImport.moduleName);
}
if (this.params.globalName && !this.globalIdentifierName) {
// This should not happen
throw new Error("Global identifier has not been provided");

const globalNames = this.params.globalName ? [this.params.globalName] : this.params.globalNames;

const identifiers = this.getIdentifiersForMultipleRequests(moduleNames, globalNames);
if (!identifiers) {
return;
}

// If a generator function is provided, use it to generate the change
const value = this.params.generator(this.globalIdentifierName ?? this.moduleIdentifierName);
const value = this.params.generator(identifiers);
if (!value) {
return;
}
Expand Down
88 changes: 75 additions & 13 deletions src/linter/ui5Types/fix/BaseFix.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
import ts from "typescript";
import {PositionInfo} from "../../LinterContext.js";
import Fix from "./Fix.js";
import Fix, {GlobalAccessRequest, ModuleDependencyRequest} from "./Fix.js";

export interface BaseFixParams {
/**
* Name of the module to import for use in the fix
*/
moduleName?: string;

/**
* The preferred identifier to use for the module import.
* If not provided, the identifier will be derived from the module name
*/
preferredIdentifier?: string;

// Not yet implemented: Requesting multiple modules
// modules?: Omit<ModuleDependencyRequest, "usagePosition">[];

/**
* Name of a global variable to use in the fix (e.g. "document")
*
* The fix will be provided with the identifier name or property access to use via
* the setIdentifierForDependency method.
* the setIdentifierForGlobal method.
*
* For example, if there is already a conflicting identifier within the same file,
* the fix will be provided with an alternative like "globalThis.document"
Expand Down Expand Up @@ -73,8 +71,8 @@ export enum FixScope {
export default abstract class BaseFix extends Fix {
protected startPos: number | undefined;
protected endPos: number | undefined;
protected moduleIdentifierName: string | undefined;
protected globalIdentifierName: string | undefined;
protected moduleIdentifierNames: Map<string, string> | undefined;
protected globalIdentifierNames: Map<string, string> | undefined;
protected sourcePosition: PositionInfo | undefined;
protected nodeTypes: ts.SyntaxKind[] = [];

Expand Down Expand Up @@ -105,15 +103,79 @@ export default abstract class BaseFix extends Fix {
};
}

setIdentifierForDependency(identifier: string) {
this.moduleIdentifierName = identifier;
setIdentifierForDependency(identifier: string, moduleName: string) {
this.moduleIdentifierNames ??= new Map();
this.moduleIdentifierNames.set(moduleName, identifier);
}

setIdentifierForGlobal(identifier: string, globalName: string) {
this.globalIdentifierNames ??= new Map();
this.globalIdentifierNames.set(globalName, identifier);
}

protected getIdentifiersForSingleRequest(
moduleName: string | undefined, globalName: string | undefined
): string | undefined {
if (moduleName) {
if (!this.moduleIdentifierNames?.has(moduleName)) {
// The identifier for the requested module has not been set
// This can happen for example if the position of the autofix is not inside
// a module definition or require block. Therefore the required dependency can not be added
// and the fix can not be applied.
return;
}
return this.moduleIdentifierNames.get(moduleName)!;
} else if (globalName) {
if (!this.globalIdentifierNames?.has(globalName)) {
// This should not happen, globals can always be provided
throw new Error("Global identifier has not been provided");
}
return this.globalIdentifierNames.get(globalName)!;
}
}

setIdentifierForGlobal(identifier: string) {
this.globalIdentifierName = identifier;
/**
* Helper method for fix classes that feature multiple imports/globals.
*
* Returns undefined if any of the requested identifiers could not be set, indicating that the
* fix must not be applied
*/
protected getIdentifiersForMultipleRequests(
moduleNames: string[] | undefined, globalNames: string[] | undefined
): string[] | undefined {
const identifiers = []; // Modules first, then globals. Both in the order they have been requested in
if (moduleNames?.length) {
if (!this.moduleIdentifierNames) {
// No modules have been set. Change can not be applied
return;
}
for (const moduleName of moduleNames) {
if (!this.moduleIdentifierNames.has(moduleName)) {
// The identifier for the requested module has not been set
// Change can not be applied
return;
}
identifiers.push(this.moduleIdentifierNames.get(moduleName)!);
}
}

if (globalNames?.length) {
if (!this.globalIdentifierNames) {
// This should not happen, globals can always be provided
throw new Error("Global identifier has not been provided");
}
for (const globalName of globalNames) {
if (!this.globalIdentifierNames.has(globalName)) {
// This should not happen, globals can always be provided
throw new Error("Global identifier has not been provided");
}
identifiers.push(this.globalIdentifierNames.get(globalName)!);
}
}
return identifiers;
}

getNewModuleDependencies() {
getNewModuleDependencies(): ModuleDependencyRequest | ModuleDependencyRequest[] | undefined {
if (this.startPos === undefined) {
throw new Error("Start position is not defined");
}
Expand All @@ -127,7 +189,7 @@ export default abstract class BaseFix extends Fix {
};
}

getNewGlobalAccess() {
getNewGlobalAccess(): GlobalAccessRequest | GlobalAccessRequest[] | undefined {
if (this.startPos === undefined) {
throw new Error("Start position is not defined");
}
Expand Down
16 changes: 3 additions & 13 deletions src/linter/ui5Types/fix/CallExpressionFix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,23 +57,13 @@ export default class CallExpressionFix extends CallExpressionBaseFix {
if (this.startPos === undefined || this.endPos === undefined) {
throw new Error("Start or end position is not defined");
}
if (this.params.moduleName && !this.moduleIdentifierName) {
// The identifier for the requested module has not been set
// This can happen for example if the position of the autofix is not inside
// a module definition or require block. Therefore the required dependency can not be added
// and the fix can not be applied.
return;
}
if (this.params.globalName && !this.globalIdentifierName) {
// This should not happen
throw new Error("Global identifier has not been provided");
}

let value = this.globalIdentifierName ?? this.moduleIdentifierName;
if (!value) {
const identifier = this.getIdentifiersForSingleRequest(this.params.moduleName, this.params.globalName);
if (!identifier) {
return;
}

let value = identifier;
if (this.params.propertyAccess) {
// If a property is defined, we need to access it on the identifier
value = `${value}.${this.params.propertyAccess}`;
Expand Down
Loading