Skip to content

Commit

Permalink
feat: Add Ui5LinterEngine
Browse files Browse the repository at this point in the history
JIRA: CPOUI5FOUNDATION-930
  • Loading branch information
matz3 committed Feb 18, 2025
1 parent 24f6ea7 commit 6b9f6b3
Show file tree
Hide file tree
Showing 10 changed files with 1,190 additions and 4,243 deletions.
59 changes: 58 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@
- [Directives](#directives)
- [Specifying Rules](#specifying-rules)
- [Scope](#scope)
- [Node.js API](#nodejs-api)
- [`ui5lint`](#ui5lint)
- [`Ui5LinterEngine`](#ui5linterengine)
- [Internals](#internals)
- [Support, Feedback, Contributing](#support-feedback-contributing)
- [Security / Disclosure](#security--disclosure)
Expand Down Expand Up @@ -264,7 +267,61 @@ An explanation why a rule is disabled can be added after the rule name; it must

### Scope

Directives are currently supported in JavaScript and TypeScript files only; they are **not** supported in XML, YAML, HTML, or any other type of file.
Directives are currently supported in JavaScript and TypeScript files only; they are **not** supported in XML, YAML, HTML, or any other type of file.

## Node.js API

### `ui5lint`

The `ui5lint` function is the main entry point for the UI5 linter. It resolves with an array of `LinterResult` objects, identical to the CLI output with the `--format json` option.
See the [src/index.ts](./src/index.ts) file for the available options.

```js
import {ui5lint} from "@ui5/linter";

// Run the linter with default options
await ui5lint();

// Run the linter with custom options
await ui5lint({
filePatterns: ["webapp/**/*.xml"],
ignorePatterns: ["webapp/thirdparty/"],
details: true,
config: "ui5lint-foo.config.mjs",
noConfig: true,
coverage: true,
ui5Config: "ui5-lint.yaml",
rootDir: "/path/to/project",
});
```

### `Ui5LinterEngine`

The `Ui5LinterEngine` class can be used to run `ui5lint` multiple times with different options while reusing and caching common parts to improve performance.

**Note:** The `lint` method can only be called once at a time. If you want to lint multiple projects in parallel, use worker threads or separate processes. The linting process is CPU-heavy and there is no benefit in parallelizing within the same Node.js process (single-threaded).

```js
import {Ui5LinterEngine} from "@ui5/linter";

const linterEngine = new Ui5LinterEngine();

// Run the linter with default options
await linterEngine.lint();

// Run the linter with custom options
await linterEngine.lint({
filePatterns: ["webapp/**/*.xml"],
ignorePatterns: ["webapp/thirdparty/"],
details: true,
config: "ui5lint-foo.config.mjs",
noConfig: true,
coverage: true,
ui5Config: "ui5-lint.yaml",
rootDir: "/path/to/project",
});

```

## Internals

Expand Down
61 changes: 40 additions & 21 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {lintProject} from "./linter/linter.js";
import type {LintResult} from "./linter/LinterContext.js";
import SharedLanguageService from "./linter/ui5Types/SharedLanguageService.js";

export type {LintResult} from "./linter/LinterContext.js";
export type {LintResult};

// Define a separate interface for the Node API as there could be some differences
// in the options and behavior compared to LinterOptions internal type.
Expand Down Expand Up @@ -47,25 +47,44 @@ export interface UI5LinterOptions {
}

export async function ui5lint(options?: UI5LinterOptions): Promise<LintResult[]> {
const {
filePatterns,
ignorePatterns = [],
details = false,
config,
noConfig,
coverage = false,
ui5Config,
rootDir = process.cwd(),
} = options ?? {};
return new Ui5LinterEngine().lint(options);
}

export class Ui5LinterEngine {
private sharedLanguageService = new SharedLanguageService();
private lintingInProgress = false;

async lint(options?: UI5LinterOptions): Promise<LintResult[]> {
if (this.lintingInProgress) {
throw new Error("Linting is already in progress");
}
this.lintingInProgress = true;

const {
filePatterns,
ignorePatterns = [],
details = false,
config,
noConfig,
coverage = false,
ui5Config,
rootDir = process.cwd(),
} = options ?? {};

return lintProject({
rootDir,
filePatterns,
ignorePatterns,
coverage,
details,
configPath: config,
noConfig,
ui5Config,
}, new SharedLanguageService());
try {
return await lintProject({
rootDir,
filePatterns,
ignorePatterns,
coverage,
details,
configPath: config,
noConfig,
ui5Config,
}, this.sharedLanguageService);
} finally {
// Ensure that the flag is reset even if an error occurs
this.lintingInProgress = false;
}
}
}
25 changes: 25 additions & 0 deletions test/lib/index.integration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import test from "ava";
import path from "node:path";
import {fileURLToPath} from "node:url";
import {
preprocessLintResultsForSnapshot,
} from "./linter/_linterHelper.js";
import {ui5lint} from "../../src/index.js";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const fixturesBasePath = path.join(__dirname, "..", "fixtures", "linter");
const fixturesProjectsPath = path.join(fixturesBasePath, "projects");

test.serial("ui5lint API: Simultaneously test different projects", async (t) => {
const appPath = path.join(fixturesProjectsPath, "com.ui5.troublesome.app");
const libPath = path.join(fixturesProjectsPath, "library.with.custom.paths");

const results = await Promise.all([
ui5lint({rootDir: appPath}),
ui5lint({rootDir: libPath}),
]);

results.forEach((res) => {
t.snapshot(preprocessLintResultsForSnapshot(res));
});
});
Loading

0 comments on commit 6b9f6b3

Please sign in to comment.