diff --git a/src/linter/ui5Types/SourceFileLinter.ts b/src/linter/ui5Types/SourceFileLinter.ts index bed777ef4..c13553be8 100644 --- a/src/linter/ui5Types/SourceFileLinter.ts +++ b/src/linter/ui5Types/SourceFileLinter.ts @@ -38,6 +38,19 @@ function isSourceFileOfTypeScriptLib(sourceFile: ts.SourceFile) { return sourceFile.fileName.startsWith("/types/typescript/lib/"); } +/** + * Removes surrounding quote characters ("'`) from a string if they exist. + * @param {string} str + * @returns {string} + * @example removeQuotes("\"myString\"") -> "myString" + * @example removeQuotes("\'myString\'") -> "myString" + * @example removeQuotes("\`myString\`") -> "myString" + */ +function removeQuotes(str: string | undefined): string { + if (!str) return ""; + return str.replace(/^['"`]|['"`]$/g, ""); +} + export default class SourceFileLinter { #resourcePath: ResourcePath; #sourceFile: ts.SourceFile; @@ -147,6 +160,8 @@ export default class SourceFileLinter { ts.forEachChild(node, visitMetadataNodes); } else if (this.isUi5ClassDeclaration(node, "sap/ui/core/Control")) { this.analyzeControlRendererDeclaration(node); + } else if (ts.isPropertyAssignment(node) && removeQuotes(node.name.getText()) === "theme") { + this.analyzeTestsuiteThemeProperty(node); } // Traverse the whole AST from top to bottom @@ -1196,4 +1211,43 @@ export default class SourceFileLinter { return !declarations.some((declaration) => isSourceFileOfTypeScriptLib(declaration.getSourceFile())) && declarations.some((declaration) => checkFunction(declaration.getSourceFile())); } + + analyzeTestsuiteThemeProperty(node: ts.PropertyAssignment) { + // In a Test Starter testsuite file, + // themes can be defined as default (1.) or for test configs individually (2.). + + // (1.) and (2.) are checks for these two possible structures, + // which use surrounding property names to determine the context. + + // We cannot use the best practice file name "testsuite.qunit.js/ts", + // to determine if a file is a Test Starter testsuite file, + // because the file name can be arbitrary. + // Therefore, we need checks (1.) and (2.) and set a flag to true afterwards. + let isTestStarterStructure = false; + + const oneLayerUp = node.parent.parent; + const twoLayersUp = oneLayerUp?.parent.parent; + const threeLayersUp = twoLayersUp?.parent.parent; + // Check if "theme" property is inside "ui5: {...}" object + if (oneLayerUp && ts.isObjectLiteralElement(oneLayerUp) && + removeQuotes(oneLayerUp.name?.getText()) === "ui5") { + if (ts.isObjectLiteralElement(twoLayersUp) && + removeQuotes(twoLayersUp.name?.getText()) === "defaults") { + // (1.) set flag to true if "theme" property is in "defaults: {...}" context + isTestStarterStructure = true; + } else if (ts.isObjectLiteralElement(twoLayersUp) && + ts.isObjectLiteralElement(threeLayersUp) && + removeQuotes(threeLayersUp.name?.getText()) === "tests") { + // (2.) set flag to true if "theme" property is in "tests: {...}" context + isTestStarterStructure = true; + } + } + + const themeName = removeQuotes(node.initializer.getText()); + if (isTestStarterStructure && deprecatedThemes.includes(themeName)) { + this.#reporter.addMessage(MESSAGE.DEPRECATED_THEME, { + themeName, + }, node); + } + } } diff --git a/test/fixtures/linter/rules/NoDeprecatedApi/testsuite.qunit.js b/test/fixtures/linter/rules/NoDeprecatedApi/testsuite.qunit.js new file mode 100644 index 000000000..46ecc6984 --- /dev/null +++ b/test/fixtures/linter/rules/NoDeprecatedApi/testsuite.qunit.js @@ -0,0 +1,34 @@ +sap.ui.define(function () { + "use strict"; + return { + name: "QUnit test suite with deprecated themes (JS)", + defaults: { + page: "ui5://test-resources/sap/ui/demo/todo/Test.qunit.html?testsuite={suite}&test={name}", + qunit: { + version: 2 + }, + sinon: { + version: 4 + }, + ui5: { + language: "EN", + theme: "sap_bluecrystal" // positive finding + } + }, + tests: { + "unit/unitTests": { + theme: "sap_belize", // negative finding (wrong place) + title: "Unit tests for Todo App", + ui5: { + theme: "sap_belize_plus", // positive finding + } + }, + "integration/opaTests": { + title: "Integration tests for Todo App", + ui5: { + theme: "sap_belize_hcb", // positive finding + } + } + } + }; +}); diff --git a/test/fixtures/linter/rules/NoDeprecatedApi/testsuite.qunit.ts b/test/fixtures/linter/rules/NoDeprecatedApi/testsuite.qunit.ts new file mode 100644 index 000000000..d4549ba2c --- /dev/null +++ b/test/fixtures/linter/rules/NoDeprecatedApi/testsuite.qunit.ts @@ -0,0 +1,36 @@ +export default { + name: "QUnit test suite with deprecated themes (TS)", + defaults: { + page: "ui5://test-resources/ui5/walkthrough/Test.qunit.html?testsuite={suite}&test={name}", + qunit: { + version: 2 + }, + sinon: { + version: 4 + }, + ui5: { + language: "EN", + theme: "sap_belize" // positive finding + } + }, + tests: { + "unit/unitTests": { + title: "UI5 TypeScript Walkthrough - Unit Tests", + ui5: { + theme: "sap_bluecrystal" // positive finding + } + }, + "integration/opaTests": { + title: "Integration tests for Todo App", + ui5: { + theme: "sap_horizon", // negative finding + } + }, + anythingHere: { + title: "Without string property name", + ui5: { + theme: "sap_belize_hcb", // positive finding + } + } + } +}; diff --git a/test/fixtures/linter/rules/NoDeprecatedApi/testsuite2.qunit.js b/test/fixtures/linter/rules/NoDeprecatedApi/testsuite2.qunit.js new file mode 100644 index 000000000..86c7ae78a --- /dev/null +++ b/test/fixtures/linter/rules/NoDeprecatedApi/testsuite2.qunit.js @@ -0,0 +1,34 @@ +sap.ui.define(function () { + "use strict"; + return { + name: "QUnit test suite with deprecated themes (JS) - Property names are surrounded by quotes", + "defaults": { + page: "ui5://test-resources/sap/ui/demo/todo/Test.qunit.html?testsuite={suite}&test={name}", + qunit: { + version: 2 + }, + sinon: { + version: 4 + }, + "ui5": { + language: "EN", + "theme": `sap_belize_hcw`, // positive finding (with backticks) + } + }, + 'tests': { // mixture of "" and '' + 'unit/unitTests': { + theme: "sap_belize", // negative finding (wrong place) + title: "Unit tests for Todo App", + "ui5": { + "theme": 'sap_belize_plus', // positive finding + } + }, + "integration/opaTests": { + title: "Integration tests for Todo App", + 'ui5': { + 'theme': "sap_belize_hcb", // positive finding + } + } + } + }; +}); diff --git a/test/lib/linter/rules/snapshots/NoDeprecatedApi.ts.md b/test/lib/linter/rules/snapshots/NoDeprecatedApi.ts.md index 0dfe1d323..737d7b55b 100644 --- a/test/lib/linter/rules/snapshots/NoDeprecatedApi.ts.md +++ b/test/lib/linter/rules/snapshots/NoDeprecatedApi.ts.md @@ -2756,6 +2756,126 @@ Generated by [AVA](https://avajs.dev). }, ] +## General: testsuite.qunit.js + +> Snapshot 1 + + [ + { + coverageInfo: [], + errorCount: 3, + fatalErrorCount: 0, + filePath: 'testsuite.qunit.js', + messages: [ + { + column: 5, + line: 15, + message: 'Use of deprecated theme \'sap_bluecrystal\'', + messageDetails: 'Deprecated Themes and Libraries (https://ui5.sap.com/#/topic/a87ca843bcee469f82a9072927a7dcdb)', + ruleId: 'no-deprecated-theme', + severity: 2, + }, + { + column: 6, + line: 23, + message: 'Use of deprecated theme \'sap_belize_plus\'', + messageDetails: 'Deprecated Themes and Libraries (https://ui5.sap.com/#/topic/a87ca843bcee469f82a9072927a7dcdb)', + ruleId: 'no-deprecated-theme', + severity: 2, + }, + { + column: 6, + line: 29, + message: 'Use of deprecated theme \'sap_belize_hcb\'', + messageDetails: 'Deprecated Themes and Libraries (https://ui5.sap.com/#/topic/a87ca843bcee469f82a9072927a7dcdb)', + ruleId: 'no-deprecated-theme', + severity: 2, + }, + ], + warningCount: 0, + }, + ] + +## General: testsuite.qunit.ts + +> Snapshot 1 + + [ + { + coverageInfo: [], + errorCount: 3, + fatalErrorCount: 0, + filePath: 'testsuite.qunit.ts', + messages: [ + { + column: 4, + line: 13, + message: 'Use of deprecated theme \'sap_belize\'', + messageDetails: 'Deprecated Themes and Libraries (https://ui5.sap.com/#/topic/a87ca843bcee469f82a9072927a7dcdb)', + ruleId: 'no-deprecated-theme', + severity: 2, + }, + { + column: 5, + line: 20, + message: 'Use of deprecated theme \'sap_bluecrystal\'', + messageDetails: 'Deprecated Themes and Libraries (https://ui5.sap.com/#/topic/a87ca843bcee469f82a9072927a7dcdb)', + ruleId: 'no-deprecated-theme', + severity: 2, + }, + { + column: 5, + line: 32, + message: 'Use of deprecated theme \'sap_belize_hcb\'', + messageDetails: 'Deprecated Themes and Libraries (https://ui5.sap.com/#/topic/a87ca843bcee469f82a9072927a7dcdb)', + ruleId: 'no-deprecated-theme', + severity: 2, + }, + ], + warningCount: 0, + }, + ] + +## General: testsuite2.qunit.js + +> Snapshot 1 + + [ + { + coverageInfo: [], + errorCount: 3, + fatalErrorCount: 0, + filePath: 'testsuite2.qunit.js', + messages: [ + { + column: 5, + line: 15, + message: 'Use of deprecated theme \'sap_belize_hcw\'', + messageDetails: 'Deprecated Themes and Libraries (https://ui5.sap.com/#/topic/a87ca843bcee469f82a9072927a7dcdb)', + ruleId: 'no-deprecated-theme', + severity: 2, + }, + { + column: 6, + line: 23, + message: 'Use of deprecated theme \'sap_belize_plus\'', + messageDetails: 'Deprecated Themes and Libraries (https://ui5.sap.com/#/topic/a87ca843bcee469f82a9072927a7dcdb)', + ruleId: 'no-deprecated-theme', + severity: 2, + }, + { + column: 6, + line: 29, + message: 'Use of deprecated theme \'sap_belize_hcb\'', + messageDetails: 'Deprecated Themes and Libraries (https://ui5.sap.com/#/topic/a87ca843bcee469f82a9072927a7dcdb)', + ruleId: 'no-deprecated-theme', + severity: 2, + }, + ], + warningCount: 0, + }, + ] + ## General: ui5.yaml > Snapshot 1 diff --git a/test/lib/linter/rules/snapshots/NoDeprecatedApi.ts.snap b/test/lib/linter/rules/snapshots/NoDeprecatedApi.ts.snap index b3446ee3f..eeef2a9e5 100644 Binary files a/test/lib/linter/rules/snapshots/NoDeprecatedApi.ts.snap and b/test/lib/linter/rules/snapshots/NoDeprecatedApi.ts.snap differ