From 161f157f56d88d614e9f673b737856148e42bcbc Mon Sep 17 00:00:00 2001 From: Max Reichmann <91143102+maxreichmann@users.noreply.github.com> Date: Tue, 30 Jul 2024 09:49:52 +0200 Subject: [PATCH] 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: https://github.com/SAP/ui5-linter/pull/197 --------- Co-authored-by: Yavor Ivanov --- src/linter/dotLibrary/DotLibraryLinter.ts | 83 ++++++++++++++++++ src/linter/dotLibrary/linter.ts | 28 ++++++ src/linter/html/parser.ts | 49 +---------- src/linter/lintWorkspace.ts | 2 + src/utils/xmlParser.ts | 46 ++++++++++ .../src/main/js/.library | 22 +++++ test/lib/linter/snapshots/linter.ts.md | 33 +++++++ test/lib/linter/snapshots/linter.ts.snap | Bin 8327 -> 8524 bytes test/lib/utils/xmlParser.ts | 50 +++++++++++ 9 files changed, 267 insertions(+), 46 deletions(-) create mode 100644 src/linter/dotLibrary/DotLibraryLinter.ts create mode 100644 src/linter/dotLibrary/linter.ts create mode 100644 src/utils/xmlParser.ts create mode 100644 test/lib/utils/xmlParser.ts diff --git a/src/linter/dotLibrary/DotLibraryLinter.ts b/src/linter/dotLibrary/DotLibraryLinter.ts new file mode 100644 index 00000000..364919db --- /dev/null +++ b/src/linter/dotLibrary/DotLibraryLinter.ts @@ -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 { + 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), + }); + } + }); + } +} diff --git a/src/linter/dotLibrary/linter.ts b/src/linter/dotLibrary/linter.ts new file mode 100644 index 00000000..fc13d7ca --- /dev/null +++ b/src/linter/dotLibrary/linter.ts @@ -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(); + })); +} diff --git a/src/linter/html/parser.ts b/src/linter/html/parser.ts index ef3d1359..2d64e47b 100644 --- a/src/linter/html/parser.ts +++ b/src/linter/html/parser.ts @@ -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") { diff --git a/src/linter/lintWorkspace.ts b/src/linter/lintWorkspace.ts index 2a15f472..b45f1497 100644 --- a/src/linter/lintWorkspace.ts +++ b/src/linter/lintWorkspace.ts @@ -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"; @@ -22,6 +23,7 @@ export default async function lintWorkspace( lintJson(params), lintHtml(params), lintUI5Yaml(params), + lintDotLibrary(params), ]); const typeLinter = new TypeLinter(params); diff --git a/src/utils/xmlParser.ts b/src/utils/xmlParser.ts new file mode 100644 index 00000000..4e1f142c --- /dev/null +++ b/src/utils/xmlParser.ts @@ -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(); +} diff --git a/test/fixtures/linter/projects/library.with.custom.paths/src/main/js/.library b/test/fixtures/linter/projects/library.with.custom.paths/src/main/js/.library index ffbfd683..7663d30a 100644 --- a/test/fixtures/linter/projects/library.with.custom.paths/src/main/js/.library +++ b/test/fixtures/linter/projects/library.with.custom.paths/src/main/js/.library @@ -4,9 +4,31 @@ SAP SE ${version} ${copyright} + + sap.ui.ca + + sap.ui.ca + + + sap.ui.ca + + + + + + sap.ui.core + + sap.ca.scfld.md + + + sap.ca.scfld.md + + + sap.ca.ui + diff --git a/test/lib/linter/snapshots/linter.ts.md b/test/lib/linter/snapshots/linter.ts.md index 7a1f234a..c01d6fc7 100644 --- a/test/lib/linter/snapshots/linter.ts.md +++ b/test/lib/linter/snapshots/linter.ts.md @@ -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: 25, + message: 'Use of deprecated library \'sap.ca.scfld.md\'', + ruleId: 'ui5-linter-no-deprecated-library', + severity: 2, + }, + { + column: 4, + fatal: undefined, + line: 28, + message: 'Use of deprecated library \'sap.ca.scfld.md\'', + ruleId: 'ui5-linter-no-deprecated-library', + severity: 2, + }, + { + column: 4, + fatal: undefined, + line: 31, + message: 'Use of deprecated library \'sap.ca.ui\'', + ruleId: 'ui5-linter-no-deprecated-library', + severity: 2, + }, + ], + warningCount: 0, + }, { coverageInfo: [], errorCount: 0, diff --git a/test/lib/linter/snapshots/linter.ts.snap b/test/lib/linter/snapshots/linter.ts.snap index eb1fca48fcaf38d9bad9c3133604431d734a35ae..5e688adcf45fd1fd3fbe9e43f28e3e4f200f518f 100644 GIT binary patch literal 8524 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 G!vFvt*th8b literal 8327 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 { + const sampleDotLibrary = ` + + library.with.custom.paths + SAP SE + 1.0 + any + + + sap.ui.core + + + sap.ca.scfld.md + + + sap.ca.scfld.md + + + sap.ca.ui + + +`; + + // 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'"); +});