From 38eef73f6c06e7a5ef11ba38e736ec8cede72a05 Mon Sep 17 00:00:00 2001 From: Matthias Osswald Date: Fri, 10 Jan 2025 14:36:01 +0100 Subject: [PATCH] feat: Detect deprecated 'type' in `View.create` / `` This adds the detection of the partial deprecation of the View.create factory. In addition, nested views within XML that use the general tag are now also detected when using a deprecated 'type'. JIRA: CPOUI5FOUNDATION-907 --- src/linter/messages.ts | 10 + src/linter/ui5Types/SourceFileLinter.ts | 40 +++ .../generator/AbstractGenerator.ts | 15 +- .../generator/FragmentGenerator.ts | 2 +- .../xmlTemplate/generator/ViewGenerator.ts | 2 +- .../NoDeprecatedApi/PartialDeprecation.js | 56 +++- .../rules/NoDeprecatedApi/XMLView.view.xml | 14 + ...FragmentDefinitionNestedViews.fragment.xml | 18 ++ .../xml/XMLFragmentNestedViews.fragment.xml | 17 ++ .../xml/XMLViewNestedViews.view.xml | 18 ++ .../rules/snapshots/NoDeprecatedApi.ts.md | 162 ++++++++-- .../rules/snapshots/NoDeprecatedApi.ts.snap | Bin 18026 -> 18763 bytes .../xmlTemplate/snapshots/transpiler.ts.md | 286 ++++++++++++++++++ .../xmlTemplate/snapshots/transpiler.ts.snap | Bin 5967 -> 6736 bytes 14 files changed, 617 insertions(+), 23 deletions(-) create mode 100644 test/fixtures/transpiler/xml/XMLFragmentDefinitionNestedViews.fragment.xml create mode 100644 test/fixtures/transpiler/xml/XMLFragmentNestedViews.fragment.xml create mode 100644 test/fixtures/transpiler/xml/XMLViewNestedViews.view.xml diff --git a/src/linter/messages.ts b/src/linter/messages.ts index 048c9c5b6..cc34b7d71 100644 --- a/src/linter/messages.ts +++ b/src/linter/messages.ts @@ -64,6 +64,7 @@ export enum MESSAGE { PARTIALLY_DEPRECATED_ODATA_MODEL_V2_CREATE_ENTRY, PARTIALLY_DEPRECATED_ODATA_MODEL_V2_CREATE_ENTRY_PROPERTIES_ARRAY, PARTIALLY_DEPRECATED_PARAMETERS_GET, + PARTIALLY_DEPRECATED_VIEW_CREATE, PREFER_TEST_STARTER, REDUNDANT_BOOTSTRAP_PARAM, REDUNDANT_BOOTSTRAP_PARAM_ERROR, @@ -482,6 +483,15 @@ export const MESSAGE_INFO = { details: () => `{@link sap.ui.core.theming.Parameters#sap.ui.core.theming.Parameters.get Parameters.get}`, }, + [MESSAGE.PARTIALLY_DEPRECATED_VIEW_CREATE]: { + severity: LintMessageSeverity.Error, + ruleId: RULES["no-deprecated-api"], + + message: ({typeValue}: {typeValue: string}) => + `Usage of deprecated value '${typeValue}' for parameter 'type' in 'sap/ui/core/mvc/View.create'`, + details: () => `{@link sap.ui.core.mvc.View#sap.ui.core.mvc.View.create View.create}`, + }, + [MESSAGE.REDUNDANT_BOOTSTRAP_PARAM]: { severity: LintMessageSeverity.Warning, ruleId: RULES["no-deprecated-api"], diff --git a/src/linter/ui5Types/SourceFileLinter.ts b/src/linter/ui5Types/SourceFileLinter.ts index d19cf6907..1d3e8ad47 100644 --- a/src/linter/ui5Types/SourceFileLinter.ts +++ b/src/linter/ui5Types/SourceFileLinter.ts @@ -19,6 +19,8 @@ const log = getLogger("linter:ui5Types:SourceFileLinter"); // https://github.com/SAP/openui5/blob/32c21c33d9dc29a32bf7ee7f41d7bae23dcf086b/src/sap.ui.core/src/sap/ui/test/starter/_utils.js#L287 const VALID_TESTSUITE = /^\/testsuite(?:\.[a-z][a-z0-9-]*)*\.qunit\.(?:js|ts)$/; +const DEPRECATED_VIEW_TYPES = ["JS", "JSON", "HTML", "Template"]; + interface DeprecationInfo { symbol: ts.Symbol; messageDetails: string; @@ -809,6 +811,8 @@ export default class SourceFileLinter { this.#analyzeMobileInit(node); } else if (symbolName === "setTheme" && moduleName === "sap/ui/core/Theming") { this.#analyzeThemingSetTheme(node); + } else if (symbolName === "create" && moduleName === "sap/ui/core/mvc/View") { + this.#analyzeViewCreate(node); } else if (/\.qunit\.(js|ts)$/.test(this.sourceFile.fileName) && symbolName === "ready" && moduleName === "sap/ui/core/Core") { this.#reportTestStarter(node); @@ -1123,6 +1127,42 @@ export default class SourceFileLinter { } } + #analyzeViewCreate(node: ts.CallExpression) { + if (!node.arguments?.length) { + return; + } + + const optionsArg = node.arguments[0]; + + if (!ts.isObjectLiteralExpression(optionsArg)) { + return; + } + + // Find "type" property + let typeProperty; + for (const property of optionsArg.properties) { + if (!ts.isPropertyAssignment(property)) { + continue; + } + if ( + (ts.isIdentifier(property.name) || ts.isStringLiteral(property.name)) && + property.name.text === "type" + ) { + typeProperty = property; + break; + } + } + + if (typeProperty && ts.isStringLiteralLike(typeProperty.initializer)) { + const typeValue = typeProperty.initializer.text; + if (DEPRECATED_VIEW_TYPES.includes(typeValue)) { + this.#reporter.addMessage(MESSAGE.PARTIALLY_DEPRECATED_VIEW_CREATE, { + typeValue, + }, node); + } + } + } + getDeprecationInfoForAccess(node: ts.AccessExpression): DeprecationInfo | null { let symbol; if (ts.isPropertyAccessExpression(node)) { diff --git a/src/linter/xmlTemplate/generator/AbstractGenerator.ts b/src/linter/xmlTemplate/generator/AbstractGenerator.ts index 3c429a5d5..2ceb14bbc 100644 --- a/src/linter/xmlTemplate/generator/AbstractGenerator.ts +++ b/src/linter/xmlTemplate/generator/AbstractGenerator.ts @@ -32,7 +32,7 @@ export default abstract class AbstractGenerator { throw new Error("Not implemented"); } - writeControl(controlDeclaration: ControlDeclaration) { + writeControl(controlDeclaration: ControlDeclaration, rootControl = false) { const importVariableName = this._getUniqueVariableName(controlDeclaration.name); const moduleName = controlDeclaration.namespace.replaceAll(".", "/") + `/${controlDeclaration.name}`; @@ -49,7 +49,18 @@ export default abstract class AbstractGenerator { // Create the control this._body.write(`const ${controlDeclaration.variableName} = `); - this._body.writeln(`new ${importVariableName}({`, controlDeclaration.start, controlDeclaration.end); + + // Special case: Use View.create for nested views + if (!rootControl && moduleName === "sap/ui/core/mvc/View") { + this._body.writeln( + `await ${importVariableName}.create({`, controlDeclaration.start, controlDeclaration.end + ); + } else { + this._body.writeln( + `new ${importVariableName}({`, controlDeclaration.start, controlDeclaration.end + ); + } + // Write properties controlDeclaration.properties.forEach((attribute) => { // Add mapping of id to module name so that specific byId lookup for controllers can be generated. diff --git a/src/linter/xmlTemplate/generator/FragmentGenerator.ts b/src/linter/xmlTemplate/generator/FragmentGenerator.ts index ea1eb3a4a..aa4afde30 100644 --- a/src/linter/xmlTemplate/generator/FragmentGenerator.ts +++ b/src/linter/xmlTemplate/generator/FragmentGenerator.ts @@ -6,7 +6,7 @@ export default class FragmentGenerator extends AbstractGenerator { let returnVal; if (controlInfo.kind === NodeKind.Control) { - this.writeControl(controlInfo); + this.writeControl(controlInfo, true); returnVal = controlInfo.variableName; } else if (controlInfo.kind === NodeKind.FragmentDefinition) { const variables = Array.from(controlInfo.controls.values()).map((control) => { diff --git a/src/linter/xmlTemplate/generator/ViewGenerator.ts b/src/linter/xmlTemplate/generator/ViewGenerator.ts index a6dd4674b..536804dd2 100644 --- a/src/linter/xmlTemplate/generator/ViewGenerator.ts +++ b/src/linter/xmlTemplate/generator/ViewGenerator.ts @@ -11,6 +11,6 @@ export default class ViewGenerator extends AbstractGenerator { } }); this._body.write(`export default `); - this.writeControl(controlInfo); + this.writeControl(controlInfo, true); } } diff --git a/test/fixtures/linter/rules/NoDeprecatedApi/PartialDeprecation.js b/test/fixtures/linter/rules/NoDeprecatedApi/PartialDeprecation.js index 0622477cc..0404bdb38 100644 --- a/test/fixtures/linter/rules/NoDeprecatedApi/PartialDeprecation.js +++ b/test/fixtures/linter/rules/NoDeprecatedApi/PartialDeprecation.js @@ -5,8 +5,10 @@ sap.ui.define([ "sap/ui/model/odata/v2/ODataModel", "sap/ui/core/Component", "sap/ui/core/routing/Router", - "sap/ui/util/Mobile" -], function(Parameters, JSONModel, ODataModelV4, ODataModelV2, Component, Router, Mobile) { + "sap/ui/util/Mobile", + "sap/ui/core/mvc/View", + "sap/ui/core/mvc/ViewType" +], function(Parameters, JSONModel, ODataModelV4, ODataModelV2, Component, Router, Mobile, View, ViewType) { Parameters.get(); // (deprecated since 1.92) If no parameter is given Parameters.get("sapUiParam1"); // (deprecated since 1.94) If a string is given as first parameter @@ -75,4 +77,54 @@ sap.ui.define([ homeIconPrecomposed: true, // Deprecated }); Mobile.init({}); // Negative test: No deprecated parameters + + View.create({ + type: "JS", // Deprecated type + viewName: "myapp.view.Home" + }); + View.create({ + type: ViewType.JS, // Deprecated type + viewName: "myapp.view.Home" + }); + + View.create({ + type: "JSON", // Deprecated type + viewName: "myapp.view.Home" + }); + View.create({ + type: ViewType.JSON, // Deprecated type + viewName: "myapp.view.Home" + }); + + View.create({ + type: "HTML", // Deprecated type + viewName: "myapp.view.Home" + }); + View.create({ + type: ViewType.HTML, // Deprecated type + viewName: "myapp.view.Home" + }); + + View.create({ + type: "Template", // Deprecated type + viewName: "myapp.view.Home" + }); + View.create({ + type: ViewType.Template, // Deprecated type + viewName: "myapp.view.Home" + }); + + // Negative tests: No deprecated types + View.create({ + viewName: "module:myapp.view.Home" + }); + View.create({ + type: "XML", + viewName: "myapp.view.Home" + }); + View.create({ + type: ViewType.XML, + viewName: "myapp.view.Home" + }); + }); diff --git a/test/fixtures/linter/rules/NoDeprecatedApi/XMLView.view.xml b/test/fixtures/linter/rules/NoDeprecatedApi/XMLView.view.xml index 7980d696d..e3458bab2 100644 --- a/test/fixtures/linter/rules/NoDeprecatedApi/XMLView.view.xml +++ b/test/fixtures/linter/rules/NoDeprecatedApi/XMLView.view.xml @@ -18,4 +18,18 @@