Skip to content

feat(autofix): Fix complex deprecated Configuration APIs #685

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 7 commits into
base: autofix-basic-configuration-apis
Choose a base branch
from
Draft
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
26 changes: 26 additions & 0 deletions src/autofix/solutions/codeReplacer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,32 @@ function patchMessageFixHints(fixHints?: FixHints, apiName?: string) {
`$moduleIdentifier.${fnName}(${cleanRedundantArguments(fixHints.exportCodeToBeUsed.args)})`;
}
}
} else if ([
"setCalendarType",
"setCalendarWeekNumbering",
"setFormatLocale",
"setLanguage",
"setRTL",
"setTheme",
"setTimezone",
].includes(apiName ?? "") &&
[
"sap/base/i18n/Formatting",
"sap/base/i18n/Localization",
"sap/ui/core/Theming",
].includes(fixHints?.moduleName ?? "")) {
if (fixHints?.exportCodeToBeUsed.isExpectedValue) {
// API not compatible
fixHints = undefined;
log.verbose(`Autofix skipped for ${apiName}.`);
} else {
const fnName = apiName ?? "";
if (fnName && fixHints && typeof fixHints.exportCodeToBeUsed === "object" &&
fixHints.exportCodeToBeUsed.args) {
fixHints.exportCodeToBeUsed.name =
`$moduleIdentifier.${fnName}(${cleanRedundantArguments(fixHints.exportCodeToBeUsed.args)})`;
}
}
}

return fixHints;
Expand Down
7 changes: 6 additions & 1 deletion src/linter/ui5Types/SourceFileLinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -916,7 +916,8 @@ export default class SourceFileLinter {
ts.isPropertyAccessExpression(exprNode) ||
ts.isCallExpression(exprNode)) {
fixHints = this.getJquerySapFixHints(exprNode) ??
this.getCoreFixHints(exprNode, deprecationInfo.ui5TypeInfo);
this.getCoreFixHints(exprNode, deprecationInfo.ui5TypeInfo) ??
this.getConfigFixHints(exprNode, deprecationInfo.ui5TypeInfo);
}
this.#reporter.addMessage(MESSAGE.DEPRECATED_FUNCTION_CALL, {
functionName: propName,
Expand Down Expand Up @@ -1795,4 +1796,8 @@ export default class SourceFileLinter {
getCoreFixHints(node: ts.CallExpression | ts.AccessExpression, ui5TypeInfo?: Ui5TypeInfo) {
return this.#fixHintsGenerator?.getCoreFixHints(node, ui5TypeInfo);
}

getConfigFixHints(node: ts.CallExpression | ts.AccessExpression, ui5TypeInfo?: Ui5TypeInfo) {
return this.#fixHintsGenerator?.getConfigFixHints(node, ui5TypeInfo);
}
}
158 changes: 158 additions & 0 deletions src/linter/ui5Types/fixHints/ConfigurationFixHintsGenerator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import ts from "typescript";
import type {ExportCodeToBeUsed, FixHints} from "./FixHints.js";
import {isExpectedValueExpression, Ui5TypeInfoKind} from "../utils/utils.js";
import type {Ui5TypeInfo} from "../utils/utils.js";

const configurationModulesReplacements = new Map<string, FixHints>([
// https://github.com/SAP/ui5-linter/issues/620
["getAccessibility", {
moduleName: "sap/ui/core/ControlBehavior", exportNameToBeUsed: "isAccessibilityEnabled",
}],
["getActiveTerminologies", {
moduleName: "sap/base/i18n/Localization", exportNameToBeUsed: "getActiveTerminologies",
}],
["getAllowlistService", {
moduleName: "sap/ui/security/Security", exportNameToBeUsed: "getAllowlistService",
}],
["getAnimationMode", {
moduleName: "sap/ui/core/ControlBehavior", exportNameToBeUsed: "getAnimationMode",
}],
["getCalendarType", {
moduleName: "sap/base/i18n/Formatting", exportNameToBeUsed: "getCalendarType",
}],
["getCalendarWeekNumbering", {
moduleName: "sap/base/i18n/Formatting", exportNameToBeUsed: "getCalendarWeekNumbering",
}],
["getFrameOptions", {
moduleName: "sap/ui/security/Security", exportNameToBeUsed: "getFrameOptions",
}],
["getLanguage", {
moduleName: "sap/base/i18n/Localization", exportNameToBeUsed: "getLanguage",
}],
["getRTL", {
moduleName: "sap/base/i18n/Localization", exportNameToBeUsed: "getRTL",
}],
["getSAPLogonLanguage", {
moduleName: "sap/base/i18n/Localization", exportNameToBeUsed: "getSAPLogonLanguage",
}],
["getSecurityTokenHandlers", {
moduleName: "sap/ui/security/Security", exportNameToBeUsed: "getSecurityTokenHandlers",
}],
["getTheme", {
moduleName: "sap/ui/core/Theming", exportNameToBeUsed: "getTheme",
}],
["getTimezone", {
moduleName: "sap/base/i18n/Localization", exportNameToBeUsed: "getTimezone",
}],
["getUIDPrefix", {
moduleName: "sap/ui/base/ManagedObjectMetadata", exportNameToBeUsed: "getUIDPrefix",
}],
["getWhitelistService", {
moduleName: "sap/ui/security/Security", exportNameToBeUsed: "getAllowlistService",
}],
["setAnimationMode", {
moduleName: "sap/ui/core/ControlBehavior", exportNameToBeUsed: "setAnimationMode",
}],
["setSecurityTokenHandlers", {
moduleName: "sap/ui/security/Security", exportNameToBeUsed: "setSecurityTokenHandlers",
}],

// Note: Not 1:1 compatible. Does not return "this"
["setCalendarType", {
moduleName: "sap/base/i18n/Formatting", exportCodeToBeUsed: "$moduleIdentifier.setCalendarType($1)",
}],
// Note: Not 1:1 compatible. Does not return "this"
["setCalendarWeekNumbering", {
moduleName: "sap/base/i18n/Formatting", exportCodeToBeUsed: "$moduleIdentifier.setCalendarWeekNumbering($1)",
}],
// Note: Not 1:1 compatible. Does not return "this"
["setFormatLocale", {
moduleName: "sap/base/i18n/Formatting", exportCodeToBeUsed: "$moduleIdentifier.setFormatLocale($1)",
}],
// Note: Not 1:1 compatible. Does not return "this"
["setLanguage", {
moduleName: "sap/base/i18n/Localization", exportCodeToBeUsed: "$moduleIdentifier.setLanguage($1, $2)",
}],
// Note: Not 1:1 compatible. Does not return "this"
["setRTL", {
moduleName: "sap/base/i18n/Localization", exportCodeToBeUsed: "$moduleIdentifier.setRTL($1)",
}],
// Note: Not 1:1 compatible. Does not return "this"
["setTheme", {
moduleName: "sap/ui/core/Theming", exportCodeToBeUsed: "$moduleIdentifier.setTheme($1)",
}],
// Note: Not 1:1 compatible. Does not return "this"
["setTimezone", {
moduleName: "sap/base/i18n/Localization", exportCodeToBeUsed: "$moduleIdentifier.setTimezone($1)",
}],

["getLanguageTag", {
moduleName: "sap/base/i18n/Localization", exportCodeToBeUsed: "$moduleIdentifier.getLanguageTag().toString()",
}],

// TODO: Complex replacement: Discuss: Old API returns boolean, new API returns AnimationMode. How to migrate?
// (-> 2 new module imports) How to setup this map entry?
// getAnimation()

["getFormatLocale", {
moduleName: "sap/base/i18n/Formatting", exportCodeToBeUsed: "$moduleIdentifier.getFormatLocale().toString()",
}],

// TODO: Complex replacement:
// "Configuration.getLocale()" needs to be replaced with "new Locale(Localization.getLanguageTag())".
// (-> 2 new module imports) How to setup this map entry?
// getLocale()

// Migration not possible
// Old API is sync and new API is async
// getVersion()
]);

export default class ConfigurationFixHintsGenerator {
getFixHints(node: ts.CallExpression | ts.AccessExpression, ui5TypeInfo?: Ui5TypeInfo): FixHints | undefined {
if (!ts.isPropertyAccessExpression(node)) {
return undefined;
}

if (!ui5TypeInfo ||
ui5TypeInfo.kind !== Ui5TypeInfoKind.Module ||
ui5TypeInfo.module !== "sap/ui/core/Configuration") {
return undefined;
}

const methodName = "export" in ui5TypeInfo ? ui5TypeInfo.export ?? "" : "";
const moduleReplacement = configurationModulesReplacements.get(methodName);
if (!moduleReplacement) {
return undefined;
}

let exportCodeToBeUsed;
if (moduleReplacement.exportCodeToBeUsed) {
exportCodeToBeUsed = {
name: moduleReplacement.exportCodeToBeUsed,
// Check whether the return value of the call expression is assigned to a variable,
// passed to another function or used elsewhere.
isExpectedValue: isExpectedValueExpression(node),
} as ExportCodeToBeUsed;

let callExpression;
if (ts.isCallExpression(node.parent) &&
// if a prop is wrapped in a function, then current.parent is the call expression
// which is wrong. That's why check if parent expression is actually the current node
// which would ensure that the prop is actually a call expression.
node.parent.expression === node) {
callExpression = node.parent;
}

// Extract arguments from the call expression
if (callExpression) {
exportCodeToBeUsed.args = callExpression.arguments.map((arg) => ({
value: arg.getText(),
kind: arg?.kind,
}));
}
}

return {...moduleReplacement, exportCodeToBeUsed};
}
}
8 changes: 8 additions & 0 deletions src/linter/ui5Types/fixHints/FixHintsGenerator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import JquerySapFixHintsGenerator from "./JquerySapFixHintsGenerator.js";
import CoreFixHintsGenerator from "./CoreFixHintsGenerator.js";
import {FixHints} from "./FixHints.js";
import type {Ui5TypeInfo} from "../utils/utils.js";
import ConfigurationFixHintsGenerator from "./ConfigurationFixHintsGenerator.js";

export default class FixHintsGenerator {
private globalsGenerator: GlobalsFixHintsGenerator;
private jquerySapGenerator: JquerySapFixHintsGenerator;
private coreGenerator: CoreFixHintsGenerator;
private configGenerator: ConfigurationFixHintsGenerator;

constructor(
resourcePath: string,
Expand All @@ -18,6 +20,7 @@ export default class FixHintsGenerator {
this.globalsGenerator = new GlobalsFixHintsGenerator(resourcePath, ambientModuleCache);
this.jquerySapGenerator = new JquerySapFixHintsGenerator();
this.coreGenerator = new CoreFixHintsGenerator(ambientModuleCache);
this.configGenerator = new ConfigurationFixHintsGenerator();
}

public getGlobalsFixHints(node: ts.CallExpression | ts.AccessExpression): FixHints | undefined {
Expand All @@ -34,4 +37,9 @@ export default class FixHintsGenerator {
ui5TypeInfo?: Ui5TypeInfo): FixHints | undefined {
return this.coreGenerator.getFixHints(node, ui5TypeInfo);
}

public getConfigFixHints(node: ts.CallExpression | ts.AccessExpression,
ui5TypeInfo?: Ui5TypeInfo): FixHints | undefined {
return this.configGenerator.getFixHints(node, ui5TypeInfo);
}
}
8 changes: 5 additions & 3 deletions src/linter/ui5Types/utils/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,11 @@ export function isExpectedValueExpression(node: ts.Node): boolean {
node.parent.arguments.some((arg) => arg === node)) ||
// Chaining
(ts.isPropertyAccessExpression(node) &&
ts.isCallExpression(node.expression) &&
ts.isPropertyAccessExpression(node.expression.parent) &&
ts.isCallExpression(node.expression.parent.expression))
ts.isCallExpression(node.parent) && node.parent.expression === node &&
ts.isPropertyAccessExpression(node.parent.parent) &&
node.parent.parent.expression === node.parent &&
ts.isCallExpression(node.parent.parent.parent) &&
node.parent.parent.parent.expression === node.parent.parent)
) {
isExpectedValue = true;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// This test artifact contains all deprecated configuration API methods which are EASILY migratable
sap.ui.define([
"sap/ui/core/Configuration",
], (ConfigurationRenamed) => {
ConfigurationRenamed.getAccessibility();
ConfigurationRenamed.getActiveTerminologies();
ConfigurationRenamed.getAllowlistService();
ConfigurationRenamed.getAnimationMode();
ConfigurationRenamed.getCalendarType();
ConfigurationRenamed.getCalendarWeekNumbering();
ConfigurationRenamed.getFrameOptions();
ConfigurationRenamed.getLanguage();
ConfigurationRenamed.getRTL();
ConfigurationRenamed.getSAPLogonLanguage();
ConfigurationRenamed.getSecurityTokenHandlers();
ConfigurationRenamed.getTheme();
ConfigurationRenamed.getTimezone();
ConfigurationRenamed.getUIDPrefix();
ConfigurationRenamed.getWhitelistService();
ConfigurationRenamed.setAnimationMode(ConfigurationRenamed.AnimationMode.minimal);
ConfigurationRenamed.setSecurityTokenHandlers([() => {console.log("*Security token handler*");}]);
ConfigurationRenamed.getLanguageTag();
ConfigurationRenamed.getFormatLocale();


ConfigurationRenamed.setCalendarType(sCalendarType);
ConfigurationRenamed.setCalendarWeekNumbering(sCalendarWeekNumbering);
ConfigurationRenamed.setFormatLocale(sFormatLocale);
ConfigurationRenamed.setLanguage(sLanguage, sSAPLogonLanguage);
ConfigurationRenamed.setLanguage(sLanguage);
ConfigurationRenamed.setRTL(bRTL);
ConfigurationRenamed.setTheme(sTheme);
ConfigurationRenamed.setTimezone(sTimezone);

// Do not migrate these methods, as they used to return "this" and now return "undefined".
// Further more, now the functionality is moved into multiple modules.
ConfigurationRenamed.setRTL(false).setLanguage("en");
const setCalendar = (type) => ConfigurationRenamed.setCalendarType(type);
const typedCalendar = sType ? ConfigurationRenamed.setCalendarType(sType) : null;
debug("msg 2", ConfigurationRenamed.setFormatLocale(sFormatLocale));
debug("msg 2", (ConfigurationRenamed.setFormatLocale(sFormatLocale)));
debug("msg 2", ((((ConfigurationRenamed.setFormatLocale(sFormatLocale))))));
var time = ConfigurationRenamed.setTimezone(sTimezone);
var info = {
theme: ConfigurationRenamed.setTheme(sTheme)
};
ConfigurationRenamed.setTheme(sTheme) ?? ConfigurationRenamed.setTimezone(sTimezone);
ConfigurationRenamed.setCalendarWeekNumbering(sCalendarWeekNumbering) ? "a" : "b";
ConfigurationRenamed.setCalendarType(sCalendarType), ConfigurationRenamed.setCalendarWeekNumbering(sCalendarWeekNumbering);
fnCall(ConfigurationRenamed.setLanguage(sLanguage));
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// This test artifact contains all deprecated configuration API methods which are EASILY migratable
// without import (only use of globals)
sap.ui.define([], () => {
const globalConfiguration = sap.ui.getCore().getConfiguration();

globalConfiguration.getAccessibility();
sap.ui.getCore().getConfiguration().getAccessibility();

globalConfiguration.getActiveTerminologies();
sap.ui.getCore().getConfiguration().getActiveTerminologies();

globalConfiguration.getAllowlistService();
sap.ui.getCore().getConfiguration().getAllowlistService();

globalConfiguration.getAnimationMode();
sap.ui.getCore().getConfiguration().getAnimationMode();

globalConfiguration.getCalendarType();
sap.ui.getCore().getConfiguration().getCalendarType();

globalConfiguration.getCalendarWeekNumbering();
sap.ui.getCore().getConfiguration().getCalendarWeekNumbering();

globalConfiguration.getFrameOptions();
sap.ui.getCore().getConfiguration().getFrameOptions();

globalConfiguration.getLanguage();
sap.ui.getCore().getConfiguration().getLanguage();

globalConfiguration.getRTL();
sap.ui.getCore().getConfiguration().getRTL();

globalConfiguration.getSAPLogonLanguage();
sap.ui.getCore().getConfiguration().getSAPLogonLanguage();

globalConfiguration.getSecurityTokenHandlers();
sap.ui.getCore().getConfiguration().getSecurityTokenHandlers();

globalConfiguration.getTheme();
sap.ui.getCore().getConfiguration().getTheme();

globalConfiguration.getTimezone();
sap.ui.getCore().getConfiguration().getTimezone();

globalConfiguration.getUIDPrefix();
sap.ui.getCore().getConfiguration().getUIDPrefix();

globalConfiguration.getWhitelistService();
sap.ui.getCore().getConfiguration().getWhitelistService();

globalConfiguration.setAnimationMode(globalConfiguration.AnimationMode.minimal);
sap.ui.getCore().getConfiguration().setAnimationMode(sap.ui.getCore().getConfiguration().AnimationMode.minimal);

globalConfiguration.setSecurityTokenHandlers([() => {console.log("*Security token handler*");}]);
sap.ui.getCore().getConfiguration().setSecurityTokenHandlers([() => {console.log("*Security token handler*");}]);

globalConfiguration.getLanguageTag();
sap.ui.getCore().getConfiguration().getLanguageTag();

globalConfiguration.getFormatLocale();
sap.ui.getCore().getConfiguration().getFormatLocale();


sap.ui.getCore().getConfiguration().setCalendarType(sCalendarType);
sap.ui.getCore().getConfiguration().setCalendarWeekNumbering(sCalendarWeekNumbering);
sap.ui.getCore().getConfiguration().setFormatLocale(sFormatLocale);
globalConfiguration.setLanguage(sLanguage, sSAPLogonLanguage);
sap.ui.getCore().getConfiguration().setLanguage(sLanguage);
sap.ui.getCore().getConfiguration().setRTL(bRTL);
globalConfiguration.setTheme(sTheme);
sap.ui.getCore().getConfiguration().setTimezone(sTimezone);

// Do not migrate these methods, as they used to return "this" and now return "undefined".
// Further more, now the functionality is moved into multiple modules.
sap.ui.getCore().getConfiguration().setRTL(false).setLanguage("en");
const setCalendar = (type) => sap.ui.getCore().getConfiguration().setCalendarType(type);
const typedCalendar = sType ? sap.ui.getCore().getConfiguration().setCalendarType(sType) : null;
debug("msg 2", sap.ui.getCore().getConfiguration().setFormatLocale(sFormatLocale));
debug("msg 2", (globalConfiguration.setFormatLocale(sFormatLocale)));
debug("msg 2", ((((sap.ui.getCore().getConfiguration().setFormatLocale(sFormatLocale))))));
var time = sap.ui.getCore().getConfiguration().setTimezone(sTimezone);
var info = {
theme: globalConfiguration.setTheme(sTheme)
};
globalConfiguration.setTheme(sTheme) ?? sap.ui.getCore().getConfiguration().setTimezone(sTimezone);
sap.ui.getCore().getConfiguration().setCalendarWeekNumbering(sCalendarWeekNumbering) ? "a" : "b";
globalConfiguration.setCalendarType(sCalendarType), sap.ui.getCore().getConfiguration().setCalendarWeekNumbering(sCalendarWeekNumbering);
fnCall(sap.ui.getCore().getConfiguration().setLanguage(sLanguage));
});
Loading