generated from SAP/repository-template
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add detection for deprecated dependencies in .library (#104)
JIRA: CPOUI5FOUNDATION-825 +refactor: Add common `xmlParser` +refactor: Remove `htmlParser` The `Lib.init()` call detection is addressed in the following PR: #197 --------- Co-authored-by: Yavor Ivanov <yavor.ivanov@sap.com>
- Loading branch information
1 parent
5ac0984
commit 161f157
Showing
9 changed files
with
267 additions
and
46 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
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, | ||
}); | ||
} | ||
} | ||
|
||
async #parseDotLibrary(contentStream: ReadStream): Promise<SaxTag[]> { | ||
const libs = new Set(); | ||
const tagsStack: string[] = []; | ||
const libNamePath = ["library", "dependencies", "dependency"]; | ||
await parseXML(contentStream, (event, tag) => { | ||
if (!(tag instanceof SaxTag)) { | ||
return; | ||
} | ||
|
||
if (event === SaxEventType.OpenTag && !tag.selfClosing) { | ||
tagsStack.push(tag.value); | ||
} else if (event === SaxEventType.CloseTag && !tag.selfClosing) { | ||
tagsStack.pop(); | ||
} | ||
|
||
if (event === SaxEventType.CloseTag && | ||
tag.value === "libraryName") { | ||
const isMatchingPath = libNamePath.length === tagsStack.length && | ||
libNamePath.every((lib, index) => lib === tagsStack[index]); | ||
|
||
if (isMatchingPath) { | ||
libs.add(tag); | ||
} | ||
} | ||
}); | ||
|
||
return Array.from(libs) as SaxTag[]; | ||
} | ||
|
||
#analyzeDeprecatedLibs(libs: SaxTag[]) { | ||
// Check for deprecated libraries | ||
libs.forEach((lib) => { | ||
const {line, character: column} = lib.openStart; | ||
// textNodes is always an array, but it might be empty | ||
const libName = lib.textNodes[0]?.value; | ||
|
||
if (deprecatedLibraries.includes(libName)) { | ||
this.#context.addLintingMessage(this.#resourcePath, { | ||
ruleId: RULES["ui5-linter-no-deprecated-library"], | ||
severity: LintMessageSeverity.Error, | ||
fatal: undefined, | ||
line: line + 1, | ||
column: column + 1, | ||
message: formatMessage(MESSAGES.SHORT__DEPRECATED_LIBRARY, libName), | ||
}); | ||
} | ||
}); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import {LinterParameters} from "../LinterContext.js"; | ||
import DotLibraryLinter from "./DotLibraryLinter.js"; | ||
import {Resource} from "@ui5/fs"; | ||
|
||
export default async function lintDotLibrary({context, workspace}: LinterParameters) { | ||
let dotLibraryResources: Resource[]; | ||
const pathsToLint = context.getPathsToLint(); | ||
if (pathsToLint?.length) { | ||
dotLibraryResources = []; | ||
await Promise.all(pathsToLint.map(async (resourcePath) => { | ||
if (!resourcePath.endsWith(".library")) { | ||
return; | ||
} | ||
const resource = await workspace.byPath(resourcePath); | ||
if (!resource) { | ||
throw new Error(`Resource not found: ${resourcePath}`); | ||
} | ||
dotLibraryResources.push(resource); | ||
})); | ||
} else { | ||
dotLibraryResources = await workspace.byGlob("**/.library"); | ||
} | ||
|
||
await Promise.all(dotLibraryResources.map(async (resource: Resource) => { | ||
const linter = new DotLibraryLinter(resource.getPath(), resource.getStream(), context); | ||
await linter.lint(); | ||
})); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 + SaxEventType.OpenTag, 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(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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'"); | ||
}); |