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: Add detection for deprecated dependencies in .library #104

Merged
merged 27 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
9c019b0
feat: Add Linter for .library files
maxreichmann May 7, 2024
a05bdfe
feat: Add xml parser
maxreichmann Jul 12, 2024
430ac47
fix: Eslint issues
d3xter666 Jul 12, 2024
e7a0bba
fix: Remove eslint annotations
d3xter666 Jul 12, 2024
d78f1a7
feat: Detect depr. libs within .library files
maxreichmann Jul 12, 2024
8b262ac
refactor: Rename variables
maxreichmann Jul 12, 2024
f2e1065
fix: Eslint issues
d3xter666 Jul 12, 2024
da44641
test: Update snapshots
d3xter666 Jul 12, 2024
610302f
fix: Eslint issues
d3xter666 Jul 12, 2024
ca7a962
fix: Remove debug leftover
maxreichmann Jul 12, 2024
e9bd8af
test: Add test for xmlParser
maxreichmann Jul 16, 2024
2a6533d
fix: Adjust test for xmlParser
maxreichmann Jul 17, 2024
442ae21
refactor: Clean up
maxreichmann Jul 17, 2024
ce5dd32
test: Update snapshots
maxreichmann Jul 17, 2024
8f7284a
refactor: Use util consts in linter messages
maxreichmann Jul 17, 2024
a6c2dac
refactor: Use util const
maxreichmann Jul 17, 2024
a876b14
fix: Use workspace reader instead of the rootReader
d3xter666 Jul 29, 2024
0f86b32
fix: Check for empty textNodes within libraryName tag
d3xter666 Jul 29, 2024
40af990
refactor: Extend xmlParser to support also openTags
d3xter666 Jul 29, 2024
12bfaec
refactor: Detect libs only when positioned correctly within .library …
d3xter666 Jul 29, 2024
d601d26
fix: Correct Glob pattern for .library files
d3xter666 Jul 29, 2024
6d9f896
test: Add test for the additional cases
d3xter666 Jul 29, 2024
e64b03a
test: Update snapshots
d3xter666 Jul 29, 2024
9dc5ccc
refactor: Provide correct ruleId
d3xter666 Jul 29, 2024
e78d5d4
refactor: Remove trim from the check
d3xter666 Jul 29, 2024
c76dce5
refactor: Remove empty lib check
d3xter666 Jul 29, 2024
3971889
refactor: Remove slicing from stack check
d3xter666 Jul 29, 2024
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
66 changes: 66 additions & 0 deletions src/linter/dotLibrary/DotLibraryLinter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import {LintMessageSeverity} from "../LinterContext.js";
import LinterContext from "../LinterContext.js";
import {deprecatedLibraries} from "../../utils/deprecations.js";
import {SaxEventType, Tag as SaxTag} from "sax-wasm";
import {parseXML} from "../../utils/xmlParser.js";
import {ReadStream} from "node:fs";
import {RULES, MESSAGES, formatMessage} from "../linterReporting.js";

export default class DotLibraryLinter {
#contentStream;
#resourcePath;
#context: LinterContext;

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

async lint() {
try {
const dotLibraryDependencyTags = await this.#parseDotLibrary(this.#contentStream);
this.#analyzeDeprecatedLibs(dotLibraryDependencyTags);
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
this.#context.addLintingMessage(this.#resourcePath, {
severity: LintMessageSeverity.Error,
message,
ruleId: RULES["ui5-linter-parsing-error"],
fatal: true,
});
Comment on lines +26 to +31
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not covered by unit tests

}
}

async #parseDotLibrary(contentStream: ReadStream): Promise<SaxTag[]> {
const libs = new Set();
await parseXML(contentStream, (event, tag) => {
if (tag instanceof SaxTag &&
event === SaxEventType.CloseTag &&
tag.value === "libraryName") {
libs.add(tag);
}
});

return Array.from(libs) as SaxTag[];
}

#analyzeDeprecatedLibs(libs: SaxTag[]) {
// Check for deprecated libraries
libs.forEach((lib) => {
const libName = lib.textNodes[0].value;
const {line, character: column} = lib.openStart;

if (deprecatedLibraries.includes(libName)) {
this.#context.addLintingMessage(this.#resourcePath, {
ruleId: RULES["ui5-linter-no-deprecated-api"],
d3xter666 marked this conversation as resolved.
Show resolved Hide resolved
severity: LintMessageSeverity.Error,
fatal: undefined,
line: line + 1,
column: column + 1,
message: formatMessage(MESSAGES.SHORT__DEPRECATED_LIBRARY, libName),
});
}
});
}
}
29 changes: 29 additions & 0 deletions src/linter/dotLibrary/linter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {LinterParameters} from "../LinterContext.js";
import DotLibraryLinter from "./DotLibraryLinter.js";
import {Resource} from "@ui5/fs";

export default async function lintDotLibrary({context}: LinterParameters) {
let dotLibraryResources: Resource[];
const pathsToLint = context.getPathsToLint();
const reader = context.getRootReader();
d3xter666 marked this conversation as resolved.
Show resolved Hide resolved
if (pathsToLint?.length) {
dotLibraryResources = [];
await Promise.all(pathsToLint.map(async (resourcePath) => {
if (!resourcePath.endsWith(".library")) {
return;
}
const resource = await reader.byPath(resourcePath);
if (!resource) {
throw new Error(`Resource not found: ${resourcePath}`);
}
dotLibraryResources.push(resource);
}));
} else {
dotLibraryResources = await reader.byGlob("/src/**/.library");
}

await Promise.all(dotLibraryResources.map(async (resource: Resource) => {
const linter = new DotLibraryLinter(resource.getPath(), resource.getStream(), context);
await linter.lint();
}));
}
49 changes: 3 additions & 46 deletions src/linter/html/parser.ts
Original file line number Diff line number Diff line change
@@ -1,54 +1,11 @@
import type {ReadStream} from "node:fs";
import {Detail, SaxEventType, SAXParser, Tag as SaxTag} from "sax-wasm";
import {finished} from "node:stream/promises";
import fs from "node:fs/promises";
import {createRequire} from "node:module";
const require = createRequire(import.meta.url);

let saxWasmBuffer: Buffer;
async function initSaxWasm() {
if (!saxWasmBuffer) {
const saxPath = require.resolve("sax-wasm/lib/sax-wasm.wasm");
saxWasmBuffer = await fs.readFile(saxPath);
}

return saxWasmBuffer;
}

async function parseHtml(contentStream: ReadStream, parseHandler: (type: SaxEventType, tag: Detail) => void) {
const options = {highWaterMark: 32 * 1024}; // 32k chunks
const saxWasmBuffer = await initSaxWasm();
const saxParser = new SAXParser(SaxEventType.CloseTag, options);

saxParser.eventHandler = parseHandler;

// Instantiate and prepare the wasm for parsing
if (!await saxParser.prepareWasm(saxWasmBuffer)) {
throw new Error("Unknown error during WASM Initialization");
}

// stream from a file in the current directory
contentStream.on("data", (chunk: Uint8Array) => {
try {
saxParser.write(chunk);
} catch (err) {
if (err instanceof Error) {
// In case of an error, destroy the content stream to make the
// error bubble up to our callers
contentStream.destroy(err);
} else {
throw err;
}
}
});
await finished(contentStream);
saxParser.end();
}
import {SaxEventType, Tag as SaxTag} from "sax-wasm";
import {parseXML} from "../../utils/xmlParser.js";

export async function extractJSScriptTags(contentStream: ReadStream) {
const scriptTags: SaxTag[] = [];

await parseHtml(contentStream, (event, tag) => {
await parseXML(contentStream, (event, tag) => {
if (tag instanceof SaxTag &&
event === SaxEventType.CloseTag &&
tag.value === "script") {
Expand Down
2 changes: 2 additions & 0 deletions src/linter/lintWorkspace.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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 lintDotLibrary from "./dotLibrary/linter.js";
import {taskStart} from "../utils/perf.js";
import TypeLinter from "./ui5Types/TypeLinter.js";
import LinterContext, {LintResult, LinterParameters, LinterOptions} from "./LinterContext.js";
Expand All @@ -22,6 +23,7 @@ export default async function lintWorkspace(
lintJson(params),
lintHtml(params),
lintUI5Yaml(params),
lintDotLibrary(params),
]);

const typeLinter = new TypeLinter(params);
Expand Down
46 changes: 46 additions & 0 deletions src/utils/xmlParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import type {ReadStream} from "node:fs";
import {Detail, SaxEventType, SAXParser} from "sax-wasm";
import {finished} from "node:stream/promises";
import fs from "node:fs/promises";
import {createRequire} from "node:module";
const require = createRequire(import.meta.url);

let saxWasmBuffer: Buffer;
async function initSaxWasm() {
if (!saxWasmBuffer) {
const saxPath = require.resolve("sax-wasm/lib/sax-wasm.wasm");
saxWasmBuffer = await fs.readFile(saxPath);
}

return saxWasmBuffer;
}

export async function parseXML(contentStream: ReadStream, parseHandler: (type: SaxEventType, tag: Detail) => void) {
const options = {highWaterMark: 32 * 1024}; // 32k chunks
const saxWasmBuffer = await initSaxWasm();
const saxParser = new SAXParser(SaxEventType.CloseTag, options);

saxParser.eventHandler = parseHandler;

// Instantiate and prepare the wasm for parsing
if (!await saxParser.prepareWasm(saxWasmBuffer)) {
throw new Error("Unknown error during WASM Initialization");
}

// stream from a file in the current directory
contentStream.on("data", (chunk: Uint8Array) => {
try {
saxParser.write(chunk);
} catch (err) {
if (err instanceof Error) {
// In case of an error, destroy the content stream to make the
// error bubble up to our callers
contentStream.destroy(err);
} else {
throw err;
}
}
});
await finished(contentStream);
saxParser.end();
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,14 @@
<dependency>
<libraryName>sap.ui.core</libraryName>
</dependency>
<dependency>
<libraryName>sap.ca.scfld.md</libraryName>
</dependency>
<dependency>
<libraryName>sap.ca.scfld.md</libraryName>
</dependency>
<dependency>
<libraryName>sap.ca.ui</libraryName>
</dependency>
</dependencies>
</library>
33 changes: 33 additions & 0 deletions test/lib/linter/snapshots/linter.ts.md
Original file line number Diff line number Diff line change
Expand Up @@ -845,6 +845,39 @@ Generated by [AVA](https://avajs.dev).
> Snapshot 1

[
{
coverageInfo: [],
errorCount: 3,
fatalErrorCount: 0,
filePath: 'src/main/js/.library',
messages: [
{
column: 4,
fatal: undefined,
line: 12,
message: 'Use of deprecated library \'sap.ca.scfld.md\'',
ruleId: 'ui5-linter-no-deprecated-api',
severity: 2,
},
{
column: 4,
fatal: undefined,
line: 15,
message: 'Use of deprecated library \'sap.ca.scfld.md\'',
ruleId: 'ui5-linter-no-deprecated-api',
severity: 2,
},
{
column: 4,
fatal: undefined,
line: 18,
message: 'Use of deprecated library \'sap.ca.ui\'',
ruleId: 'ui5-linter-no-deprecated-api',
severity: 2,
},
],
warningCount: 0,
},
{
coverageInfo: [],
errorCount: 0,
Expand Down
Binary file modified test/lib/linter/snapshots/linter.ts.snap
Binary file not shown.
50 changes: 50 additions & 0 deletions test/lib/utils/xmlParser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import test from "ava";
import {parseXML} from "../../../src/utils/xmlParser.js";
import {ReadStream} from "node:fs";
import {Readable} from "node:stream";
import {SaxEventType, Tag as SaxTag} from "sax-wasm";

test("Test xmlParser with .library", async (t) => {
const sampleDotLibrary = `<?xml version="1.0" ?>
<library xmlns="http://www.sap.com/sap.ui.library.xsd">
<name>library.with.custom.paths</name>
<vendor>SAP SE</vendor>
<version>1.0</version>
<copyright>any</copyright>
<dependencies>
<dependency>
<libraryName>sap.ui.core</libraryName>
</dependency>
<dependency>
<libraryName>sap.ca.scfld.md</libraryName>
</dependency>
<dependency>
<libraryName>sap.ca.scfld.md</libraryName>
</dependency>
<dependency>
<libraryName>sap.ca.ui</libraryName>
</dependency>
</dependencies>
</library>`;

// Convert raw .library content into stream
const contentStream = new Readable() as ReadStream;
// eslint-disable-next-line @typescript-eslint/no-empty-function
contentStream._read = () => {};
contentStream.push(sampleDotLibrary);
contentStream.push(null);

// Call SAXParser with the contentStream
const libs: SaxTag[] = [];
await parseXML(contentStream, (event, tag) => {
if (tag instanceof SaxTag &&
event === SaxEventType.CloseTag &&
tag.value === "libraryName") {
libs.push(tag);
}
});

// Test parsed results
t.is(libs.length, 4, "Parsed .library XML should contain 4 libraries");
t.is(libs[0].textNodes[0].value, "sap.ui.core", "First library should be 'sap.ui.core'");
});