Skip to content
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

feat: Detect deprecations in ui5.yaml (in root directory) #39

Merged
merged 12 commits into from
Apr 17, 2024
Merged
1,059 changes: 286 additions & 773 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"@ui5/logger": "^3.0.0",
"@ui5/project": "^3.9.1",
"chalk": "^5.3.0",
"data-with-position": "^0.5.0",
"figures": "^6.1.0",
"he": "^1.2.0",
"json-source-map": "^0.6.1",
Expand Down
2 changes: 2 additions & 0 deletions src/linter/lintWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {AbstractAdapter} from "@ui5/fs";
import lintXml from "./xmlTemplate/linter.js";
import lintJson from "./manifestJson/linter.js";
import lintHtml from "./html/linter.js";
import lintUI5Yaml from "./yaml/linter.js";
import {taskStart} from "../util/perf.js";
import TypeLinter from "./ui5Types/TypeLinter.js";
import LinterContext, {LintResult, LinterParameters, LinterOptions} from "./LinterContext.js";
Expand All @@ -20,6 +21,7 @@ export default async function lintWorkspace(
lintXml(params),
lintJson(params),
lintHtml(params),
lintUI5Yaml(params),
]);

const typeLinter = new TypeLinter(params);
Expand Down
2 changes: 2 additions & 0 deletions src/linter/linter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,8 @@ function transformVirtualPathToFilePath(
return path.join(srcFsBasePath, posixPath.relative(srcVirBasePath, virtualPath));
} else if (testFsBasePath && testVirBasePath && virtualPath.startsWith(testVirBasePath)) {
return path.join(testFsBasePath, posixPath.relative(testVirBasePath, virtualPath));
} else if (virtualPath.startsWith("/")) {
return posixPath.relative("/", virtualPath);
} else {
throw new Error(
`Resource path ${virtualPath} is not located within the virtual source or test directories of the project`);
Expand Down
22 changes: 1 addition & 21 deletions src/linter/manifestJson/ManifestLinter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,34 +10,14 @@ import ManifestReporter from "./ManifestReporter.js";
import {LintMessageSeverity, ResourcePath} from "../LinterContext.js";
import jsonMap from "json-source-map";
import LinterContext from "../LinterContext.js";
import deprecatedLibraries from "../../utils/deprecatedLibs.js";

interface locType {
line: number;
column: number;
pos: number;
}

const deprecatedLibraries: string[] = [
"sap.ca.scfld.md",
"sap.ca.ui",
"sap.fe.common", // Internal, removed in 1.110
"sap.fe.plugins", // Internal, removed in 1.102
"sap.fe.semantics", // Internal, removed in 1.104
"sap.landvisz", // Removed in 1.120
"sap.makit",
"sap.me",
"sap.sac.grid", // Removed in 1.114
"sap.ui.commons",
"sap.ui.suite",
"sap.ui.ux3",
"sap.ui.vtm",
"sap.uiext.inbox",
"sap.webanalytics.core",
"sap.zen.commons",
"sap.zen.crosstab",
"sap.zen.dsh",
];

const deprecatedComponents: string[] = [
"sap.zen.dsh.fioriwrapper",
];
Expand Down
88 changes: 88 additions & 0 deletions src/linter/yaml/YamlLinter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import {LintMessageSeverity} from "../LinterContext.js";
import LinterContext from "../LinterContext.js";
import deprecatedLibraries from "../../utils/deprecatedLibs.js";
import {DataWithPosition, fromYaml, getPosition} from "data-with-position";

interface YamlContent extends DataWithPosition {
framework?: {
libraries?: {
name: string;
}[];
};
positionKey?: {
end: {
column: number;
line: number;
};
start: {
column: number;
line: number;
};
};
}

export default class YamlLinter {
#content;
#resourcePath;
#context: LinterContext;

constructor(resourcePath: string, content: string, context: LinterContext) {
this.#content = content;
this.#resourcePath = resourcePath;
this.#context = context;
}

// eslint-disable-next-line @typescript-eslint/require-await
async lint() {
try {
/* Support multiple documents in one Yaml file
https://sap.github.io/ui5-tooling/stable/pages/extensibility/CustomTasks/#example-custom-task-extension-defined-in-ui5-project */

// Split Yaml file into part documents by '---' separator
const partDocuments = this.#content.split(/(\r?\n|\r|\n)---/g).map((part) => part.trim());
flovogt marked this conversation as resolved.
Show resolved Hide resolved

// Calculate the starting line number of each part document
let lineNumberOffset = 0;
for (const part of partDocuments) {
if (part !== "") {
// Parse content only of the current part
const parsedYamlWithPosInfo: YamlContent = this.#parseYaml(part);
// Analyze part content with line number offset
this.#analyzeYaml(parsedYamlWithPosInfo, lineNumberOffset);
// Update line number offset for next part
lineNumberOffset += part.split(/\r?\n|\r|\n/g).length;
}
}
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
this.#context.addLintingMessage(this.#resourcePath, {
severity: LintMessageSeverity.Error,
message,
ruleId: "ui5-linter-parsing-error",
fatal: true,
});
}
}

#parseYaml(fileContent: string): YamlContent {
// Create JS object from YAML content with position information
return fromYaml(fileContent) as YamlContent;
}

#analyzeYaml(yamlObject: YamlContent, offset: number) {
// Check for deprecated libraries
yamlObject?.framework?.libraries?.forEach((lib) => {
if (deprecatedLibraries.includes(lib.name.toString())) {
const positionInfo = getPosition(lib);
this.#context.addLintingMessage(this.#resourcePath, {
ruleId: "ui5-linter-no-deprecated-api",
severity: LintMessageSeverity.Error,
fatal: undefined,
line: positionInfo.start.line + offset,
column: positionInfo.start.column,
message: `Use of deprecated library '${lib.name}'`,
});
}
});
}
}
29 changes: 29 additions & 0 deletions src/linter/yaml/linter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {LinterParameters} from "../LinterContext.js";
import YamlLinter from "./YamlLinter.js";
import {Resource} from "@ui5/fs";

export default async function lintUI5Yaml({context}: LinterParameters) {
let ui5YamlResources: Resource[];
const pathsToLint = context.getPathsToLint();
const reader = context.getRootReader();
if (pathsToLint?.length) {
ui5YamlResources = [];
await Promise.all(pathsToLint.map(async (resourcePath) => {
if (!resourcePath.endsWith(".yaml")) {
return;
}
const resource = await reader.byPath(resourcePath);
if (!resource) {
throw new Error(`Resource not found: ${resourcePath}`);
}
ui5YamlResources.push(resource);
}));
} else {
ui5YamlResources = await reader.byGlob("/{ui5.yaml,*-ui5.yaml,*.ui5.yaml,ui5-*.yaml}");
}

await Promise.all(ui5YamlResources.map(async (resource: Resource) => {
const linter = new YamlLinter(resource.getPath(), await resource.getString(), context);
await linter.lint();
}));
}
1 change: 1 addition & 0 deletions src/untyped.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ declare module "@ui5/project" {
interface Project {
getNamespace: () => ProjectNamespace;
getReader: (options: import("@ui5/fs").ReaderOptions) => import("@ui5/fs").AbstractReader;
getRootReader: () => import("@ui5/fs").AbstractReader;
getRootPath: () => string;
getSourcePath: () => string;
_testPath: string; // TODO UI5 Tooling: Expose API for optional test path
Expand Down
22 changes: 22 additions & 0 deletions src/utils/deprecatedLibs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const deprecatedLibs: string[] = [
maxreichmann marked this conversation as resolved.
Show resolved Hide resolved
"sap.ca.scfld.md",
"sap.ca.ui",
"sap.fe.common", // Internal, removed in 1.110
"sap.fe.plugins", // Internal, removed in 1.102
"sap.fe.semantics", // Internal, removed in 1.104
"sap.landvisz", // Removed in 1.120
"sap.makit",
"sap.me",
"sap.sac.grid", // Removed in 1.114
"sap.ui.commons",
"sap.ui.suite",
"sap.ui.ux3",
"sap.ui.vtm",
"sap.uiext.inbox",
"sap.webanalytics.core",
"sap.zen.commons",
"sap.zen.crosstab",
"sap.zen.dsh",
];

export default deprecatedLibs;
11 changes: 11 additions & 0 deletions test/fixtures/linter/projects/com.ui5.troublesome.app/ui5.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
specVersion: '3.0'
metadata:
name: com.ui5.troublesome.app
type: application
framework:
name: OpenUI5
version: "1.121.0"
libraries:
- name: sap.m
- name: sap.ui.core
- name: sap.landvisz
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@ framework:
version: "1.120.6"
libraries:
- name: sap.ui.core
- name: sap.landvisz
11 changes: 11 additions & 0 deletions test/fixtures/linter/rules/NoDeprecatedApi/ui5.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
specVersion: '3.0'
flovogt marked this conversation as resolved.
Show resolved Hide resolved
metadata:
name: com.ui5.troublesome.app
type: application
framework:
name: OpenUI5
version: "1.121.0"
libraries:
- name: sap.m
- name: sap.ui.core
- name: sap.landvisz
Binary file not shown.
137 changes: 137 additions & 0 deletions test/lib/linter/YamlLinter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import test from "ava";
import YamlLinter from "../../../src/linter/yaml/YamlLinter.js";
import LinterContext from "../../../src/linter/LinterContext.js";

test("Test YamlLinter report (parsing and analyzing)", async (t) => {
/* Mock resource content of ui5.yaml file,
(formatted as used in src/linter/yaml/linter.ts)
(contains relevant 'framework' property and 'libraries' sub-property),
(contains only deprecated libraries) */
const resourceContent =
`specVersion: '3.0'
metadata:
name: ava-test-ui5yamllinter
type: application
framework:
name: OpenUI5
version: "1.121.0"
libraries:
- name: sap.ca.scfld.md
- name: sap.ca.ui
- name: sap.fe.common`;

const resourcePath = "/ui5.yaml";
const projectPath = "test.yamllinter";
const context = new LinterContext({rootDir: projectPath});

// Create UI5YamlLinter instance with resource content
const linter = new YamlLinter(resourcePath, resourceContent, context);
// Run UI5YamlLinter report
await linter.lint();

const messages = context.getLintingMessages("/ui5.yaml");

// Test returned messages
t.is(messages.length, 3, "Detection of 3 deprecated libraries expected");

// Test each message
t.is(messages[0].ruleId, "ui5-linter-no-deprecated-api", `RuleId is correct`);
t.is(messages[0].message, `Use of deprecated library 'sap.ca.scfld.md'`, `Message is correct`);
t.is(messages[0].column, 7, `Column is correct`);
t.is(messages[0].line, 9, `Line is correct`);

t.is(messages[1].ruleId, "ui5-linter-no-deprecated-api", `RuleId is correct`);
t.is(messages[1].message, `Use of deprecated library 'sap.ca.ui'`, `Message is correct`);
t.is(messages[1].column, 7, `Column is correct`);
t.is(messages[1].line, 10, `Line is correct`);

t.is(messages[2].ruleId, "ui5-linter-no-deprecated-api", `RuleId is correct`);
t.is(messages[2].message, `Use of deprecated library 'sap.fe.common'`, `Message is correct`);
t.is(messages[2].column, 7, `Column is correct`);
t.is(messages[2].line, 11, `Line is correct`);
});

test("Test YamlLinter report (parsing and analyzing) with multiple documents", async (t) => {
/* Mock resource content of ui5.yaml file with multiple documents,
(formatted as used in src/linter/yaml/linter.ts)
(contains relevant 'framework' property and 'libraries' sub-property),
(contains only deprecated libraries)
(contains document separators ('---') + comments after separator) */
const resourceContent =
`--- # This is the first document part
specVersion: "3.2"
kind: extension
type: task
metadata:
name: render-markdown-files
task:
path: lib/tasks/renderMarkdownFiles.js
--- # This is the second document part
specVersion: '3.0'
metadata:
name: ava-test-ui5yamllinter
type: application
framework:
name: OpenUI5
version: "1.121.0"
libraries:
- name: sap.ca.scfld.md
- name: sap.ca.ui
- name: sap.fe.common
--- # This is the third document part
specVersion: "3.2"
kind: extension
type: task
metadata:
name: render-markdown-files
task:
path: lib/tasks/renderMarkdownFiles.js`;

const resourcePath = "/ui5.yaml";
const projectPath = "test.yamllinter";
const context = new LinterContext({rootDir: projectPath});

// Create UI5YamlLinter instance with resource content
const linter = new YamlLinter(resourcePath, resourceContent, context);
// Run UI5YamlLinter report
await linter.lint();

const messages = context.getLintingMessages("/ui5.yaml");

// Test returned messages
t.is(messages.length, 3, "Detection of 3 deprecated libraries expected");

// Test each message
t.is(messages[0].ruleId, "ui5-linter-no-deprecated-api", `RuleId is correct`);
t.is(messages[0].message, `Use of deprecated library 'sap.ca.scfld.md'`, `Message is correct`);
t.is(messages[0].column, 7, `Column is correct`);
t.is(messages[0].line, 18, `Line is correct`);

t.is(messages[1].ruleId, "ui5-linter-no-deprecated-api", `RuleId is correct`);
t.is(messages[1].message, `Use of deprecated library 'sap.ca.ui'`, `Message is correct`);
t.is(messages[1].column, 7, `Column is correct`);
t.is(messages[1].line, 19, `Line is correct`);

t.is(messages[2].ruleId, "ui5-linter-no-deprecated-api", `RuleId is correct`);
t.is(messages[2].message, `Use of deprecated library 'sap.fe.common'`, `Message is correct`);
t.is(messages[2].column, 7, `Column is correct`);
t.is(messages[2].line, 20, `Line is correct`);
});

test("Test YamlLinter report with empty ui5.yaml", async (t) => {
const resourceContent = ``;

const resourcePath = "/ui5.yaml";
const projectPath = "test.yamllinter";
const context = new LinterContext({rootDir: projectPath});

// Create UI5YamlLinter instance with resource content
const linter = new YamlLinter(resourcePath, resourceContent, context);
// Run UI5YamlLinter report
await linter.lint();

const messages = context.getLintingMessages("/ui5.yaml");

// Test returned messages
t.is(messages.length, 0, "0 messages should be reported");
});
5 changes: 3 additions & 2 deletions test/lib/linter/_linterHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ export function createTestsForFixtures(fixturesPath: string) {
if (!fileName.endsWith(".js") &&
!fileName.endsWith(".xml") &&
!fileName.endsWith(".json") &&
!fileName.endsWith(".html")) {
// Ignore non-JavaScript, non-XML, non-JSON and non-HTML files
!fileName.endsWith(".html") &&
!fileName.endsWith(".yaml")) {
// Ignore non-JavaScript, non-XML, non-JSON, non-HTML and non-YAML files
continue;
}
let testName = fileName;
Expand Down
Loading