Skip to content

Commit

Permalink
feat: Add detection for root files (e.g. ui5.yaml)
Browse files Browse the repository at this point in the history
- Add js-yaml npm library for YAML-parsing (https://www.npmjs.com/package/js-yaml)
  • Loading branch information
maxreichmann committed Apr 15, 2024
1 parent c5b0c95 commit 6d42145
Show file tree
Hide file tree
Showing 17 changed files with 616 additions and 917 deletions.
1,242 changes: 327 additions & 915 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,10 @@
"@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",
"js-yaml": "^4.1.0",
"json-source-map": "^0.6.1",
"sax-wasm": "^2.2.4",
"typescript": "5.3.x",
Expand All @@ -84,6 +86,7 @@
"@types/he": "^1.2.3",
"@types/node": "^20.12.7",
"@types/sinon": "^17.0.3",
"@types/js-yaml": "^4.0.9",
"@types/update-notifier": "^6.0.8",
"@types/yargs": "^17.0.32",
"@ui5-language-assistant/semantic-model": "^3.3.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
87 changes: 87 additions & 0 deletions src/linter/yaml/UI5YamlLinter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import {LintMessageSeverity} from "../LinterContext.js";
import LinterContext from "../LinterContext.js";
import deprecatedLibraries from "../../utils/deprecatedLibs.js";
import yaml from "js-yaml";
import {DataWithPosition, fromYaml, getPosition} from "data-with-position";

// file content schema of 'UI5Yaml' with only relevant properties
interface UI5YamlContentSchema { // extend for further detections
framework: {
libraries: {
name: string;
}[];
};
}

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

export default class UI5YamlLinter {
#content = "";
#yamlContentWithPosInfo: UI5YamlContentSchemaWithPosInfo = {};
#resourcePath = "";
#context: LinterContext;

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

// eslint-disable-next-line @typescript-eslint/require-await
async lint() {
try {
const source: UI5YamlContentSchema = this.#parseUI5Yaml(this.#content);
this.#analyzeUI5Yaml(source);
} 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,
});
}
}

#parseUI5Yaml(fileContent: string): UI5YamlContentSchema {
// Create JS object from YAML content with position information
this.#yamlContentWithPosInfo = fromYaml(fileContent) as UI5YamlContentSchemaWithPosInfo;
// Convert YAML content to JS object
return yaml.load(fileContent) as UI5YamlContentSchema;
}

#analyzeUI5Yaml(ui5YamlObject: UI5YamlContentSchema) {
// Check for deprecated libraries
if (ui5YamlObject?.framework?.libraries?.length) {
ui5YamlObject.framework.libraries.forEach((lib, index: number) => {
if (deprecatedLibraries.includes(lib.name)) {
const positionInfo = getPosition(this.#yamlContentWithPosInfo.framework!.libraries![index]);
this.#context.addLintingMessage(this.#resourcePath, {
ruleId: "ui5-linter-no-deprecated-api",
severity: LintMessageSeverity.Error,
fatal: undefined,
line: positionInfo.start.line,
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 UI5YamlLinter from "./UI5YamlLinter.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 UI5YamlLinter(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[] = [
"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'
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.
52 changes: 52 additions & 0 deletions test/lib/linter/UI5YamlLinter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import test from "ava";
import UI5YamlLinter from "../../../src/linter/yaml/UI5YamlLinter.js";
import LinterContext from "../../../src/linter/LinterContext.js";

test("Test UI5YamlLinter report (parsing and analyzing)", async (t) => {
/* Mock resource content of ui5.yaml file,
(formatted as used in src/detectors/typeChecker/index.ts - #analyzeFiles()),
(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.js"; // '.js' due to renaming in src/detectors/typeChecker/index.ts - #analyzeFiles()
const projectPath = "test.ui5yamllinter";
const context = new LinterContext({rootDir: projectPath});

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

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

// 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`);
});
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
6 changes: 6 additions & 0 deletions test/lib/linter/rules/snapshots/NoDeprecatedApi.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -1222,3 +1222,9 @@ Generated by [AVA](https://avajs.dev).
warningCount: 0,
},
]

## General: ui5.yaml

> Snapshot 1
[]
Binary file modified test/lib/linter/rules/snapshots/NoDeprecatedApi.ts.snap
Binary file not shown.
34 changes: 34 additions & 0 deletions test/lib/linter/snapshots/linter.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,23 @@ Generated by [AVA](https://avajs.dev).
> Snapshot 1
[
{
coverageInfo: [],
errorCount: 1,
fatalErrorCount: 0,
filePath: 'ui5.yaml',
messages: [
{
column: 7,
fatal: undefined,
line: 11,
message: 'Use of deprecated library \'sap.landvisz\'',
ruleId: 'ui5-linter-no-deprecated-api',
severity: 2,
},
],
warningCount: 0,
},
{
coverageInfo: [],
errorCount: 0,
Expand Down Expand Up @@ -972,6 +989,23 @@ Generated by [AVA](https://avajs.dev).
],
warningCount: 0,
},
{
coverageInfo: [],
errorCount: 1,
fatalErrorCount: 0,
filePath: 'ui5.yaml',
messages: [
{
column: 7,
fatal: undefined,
line: 15,
message: 'Use of deprecated library \'sap.landvisz\'',
ruleId: 'ui5-linter-no-deprecated-api',
severity: 2,
},
],
warningCount: 0,
},
]

## lint: All files of library with sap.f namespace
Expand Down
27 changes: 27 additions & 0 deletions test/lib/utils/deprecatedLibs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import test from "ava";
import deprecatedLibs from "../../../src/utils/deprecatedLibs.js";

test("Test Deprecated Libs constant", (t) => {
const expectedDeprecatedLibs: 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",
];
t.deepEqual(deprecatedLibs, expectedDeprecatedLibs,
"Expected deprecated libraries list should match the actual list.");
});

0 comments on commit 6d42145

Please sign in to comment.