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 documentation for all available rules #435

Merged
merged 3 commits into from
Dec 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,35 @@
[![npm Package Version](https://badge.fury.io/js/%40ui5%2Flinter.svg)](https://www.npmjs.com/package/@ui5/linter)
[![Coverage Status](https://coveralls.io/repos/github/SAP/ui5-linter/badge.svg)](https://coveralls.io/github/SAP/ui5-linter)

- [UI5 Linter](#ui5-linter)
- [Description](#description)
- [Features](#features)
- [Rules](#rules)
- [Requirements](#requirements)
- [Installation](#installation)
- [Usage](#usage)
- [Options](#options)
- [`--details`](#--details)
- [`--format`](#--format)
- [`--ignore-pattern`](#--ignore-pattern)
- [`--config`](#--config)
- [`--ui5-config`](#--ui5-config)
- [Configuration](#configuration)
- [Configuration File Location](#configuration-file-location)
- [Supported Configuration File Names](#supported-configuration-file-names)
- [Configuration File Format](#configuration-file-format)
- [ESM (ECMAScript Modules):](#esm-ecmascript-modules)
- [CommonJS:](#commonjs)
- [Configuration Options](#configuration-options)
- [Directives](#directives)
- [Specifying Rules](#specifying-rules)
- [Scope](#scope)
- [Internals](#internals)
- [Support, Feedback, Contributing](#support-feedback-contributing)
- [Security / Disclosure](#security--disclosure)
- [Code of Conduct](#code-of-conduct)
- [Licensing](#licensing)

## Description

UI5 linter is a static code analysis tool for UI5 projects.
Expand All @@ -27,6 +56,12 @@ UI5 linter scans your UI5 project and detects issues that might interfere with i
> [!NOTE]
> While UI5 linter already provides many detection features, it is not yet covering all aspects and best practices for UI5 2.x. The intention of UI5 linter is to detect as many issues as possible that a project running with UI5 2.x might be facing. However, you'll still need to test your UI5 project with UI5 2.x as soon as it is made available. To reveal additional issues, the UI5 team plans to release more versions of UI5 linter over the next months.

## Rules

UI5 linter comes with a set of predefined rules that are enabled by default. You can disable specific rules in the code via [Directives](#directives).

A list of all available rules can be found on the [Rules](./docs/Rules.md) page.

## Requirements

- [Node.js](https://nodejs.org/) Version v20.11.x, v22.0.0, or higher
Expand Down
File renamed without changes.
84 changes: 84 additions & 0 deletions docs/Rules.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
# Rules Reference

- [Rules Reference](#rules-reference)
- [async-component-flags](#async-component-flags)
- [csp-unsafe-inline-script](#csp-unsafe-inline-script)
- [no-deprecated-api](#no-deprecated-api)
- [no-deprecated-component](#no-deprecated-component)
- [no-deprecated-control-renderer-declaration](#no-deprecated-control-renderer-declaration)
- [no-deprecated-library](#no-deprecated-library)
- [no-deprecated-theme](#no-deprecated-theme)
- [no-globals](#no-globals)
- [no-pseudo-modules](#no-pseudo-modules)
- [parsing-error](#parsing-error)
- [ui5-class-declaration](#ui5-class-declaration)

## async-component-flags

Checks whether a Component is configured for asynchronous loading via the `sap.ui.core.IAsyncContentCreation` interface in the Component metadata or via `async` flags in the `manifest.json`.

**Related information**
- [Use Asynchronous Loading](https://ui5.sap.com/#/topic/676b636446c94eada183b1218a824717)
- [Component Metadata](https://ui5.sap.com/#/topic/0187ea5e2eff4166b0453b9dcc8fc64f)
- [sap.ui.core.IAsyncContentCreation](https://ui5.sap.com/1.120/#/api/sap.ui.core.IAsyncContentCreation)

## csp-unsafe-inline-script

Checks whether inline scripts are used in HTML files in accordance with Content Security Policy (CSP) best practices.

**Related information**
- [Content Security Policy](https://ui5.sap.com/#/topic/fe1a6dba940e479fb7c3bc753f92b28c)

## no-deprecated-api

Checks whether deprecated APIs, features or parameters are used in the project.

**Related information**
- [Best Practices for Developers](https://ui5.sap.com/#/topic/28fcd55b04654977b63dacbee0552712)

## no-deprecated-component

Checks for dependencies to deprecated components in `manifest.json`.

**Related information**
- [Deprecated Themes and Libraries](https://ui5.sap.com/#/topic/a87ca843bcee469f82a9072927a7dcdb)

## no-deprecated-control-renderer-declaration

Checks whether the renderer of a control is declared correctly.

## no-deprecated-library

Checks for dependencies to deprecated libraries in `manifest.json` and `ui5.yaml`.

**Related information**
- [Deprecated Themes and Libraries](https://ui5.sap.com/#/topic/a87ca843bcee469f82a9072927a7dcdb)

## no-deprecated-theme

Checks for usage of deprecated themes in the code and HTML files.

**Related information**
- [Deprecated Themes and Libraries](https://ui5.sap.com/#/topic/a87ca843bcee469f82a9072927a7dcdb)

## no-globals

Checks for the usage of global variables in the code.

**Related information**
- [Best Practices for Developers](https://ui5.sap.com/#/topic/28fcd55b04654977b63dacbee0552712)

## no-pseudo-modules

Checks for dependencies to pseudo modules in the code.

**Related information**
- [Best Practices for Loading Modules - Migrating Access to Pseudo Modules](https://ui5.sap.com/#/topic/00737d6c1b864dc3ab72ef56611491c4)

## parsing-error

Syntax/parsing errors that appear during the linting process are reported with this rule.

## ui5-class-declaration

Checks whether the declaration of UI5 classes is correct. This rule only applies to TypeScript code where built-in ECMAScript classes are used instead of an `.extend()` call.
7 changes: 3 additions & 4 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import yargs from "yargs";
import {hideBin} from "yargs/helpers";
import base from "./cli/base.js";
import {fileURLToPath} from "node:url";
import {setVersion} from "./cli/version.js";
import {getFormattedVersion, setVersionInfo} from "./cli/version.js";
import {createRequire} from "module";

export default async function () {
Expand All @@ -17,10 +17,9 @@ export default async function () {
const require = createRequire(import.meta.url);
const pkg = require("../package.json") as {version: string};
const ui5LintJsPath = fileURLToPath(new URL("../bin/ui5lint.js", import.meta.url));
const pkgVersion = `${pkg.version} (from ${ui5LintJsPath})`;

setVersion(pkgVersion);
cli.version(pkgVersion);
setVersionInfo(pkg.version, ui5LintJsPath);
cli.version(getFormattedVersion());

// Explicitly set script name to prevent windows from displaying "ui5-linter.js"
cli.scriptName("ui5lint");
Expand Down
3 changes: 2 additions & 1 deletion src/cli/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import baseMiddleware from "./middlewares/base.js";
import chalk from "chalk";
import {isLogLevelEnabled} from "@ui5/logger";
import ConsoleWriter from "@ui5/logger/writers/Console";
import {getVersion} from "./version.js";

export interface LinterArg {
coverage: boolean;
Expand Down Expand Up @@ -172,7 +173,7 @@ async function handleLint(argv: ArgumentsCamelCase<LinterArg>) {
process.stdout.write("\n");
} else if (format === "markdown") {
const markdownFormatter = new Markdown();
process.stdout.write(markdownFormatter.format(res, details));
process.stdout.write(markdownFormatter.format(res, details, getVersion()));
process.stdout.write("\n");
} else if (format === "" || format === "stylish") {
const textFormatter = new Text(rootDir);
Expand Down
4 changes: 2 additions & 2 deletions src/cli/middlewares/logger.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {setLogLevel, isLogLevelEnabled, getLogger} from "@ui5/logger";
import ConsoleWriter from "@ui5/logger/writers/Console";
import {getVersion} from "../version.js";
import {getFormattedVersion} from "../version.js";
import type {ArgumentsCamelCase} from "yargs";
/**
* Logger middleware to enable logging capabilities
Expand Down Expand Up @@ -28,7 +28,7 @@ export async function initLogger(argv: ArgumentsCamelCase) {
ConsoleWriter.init();
if (isLogLevelEnabled("verbose")) {
const log = getLogger("cli:middlewares:base");
log.verbose(`using ui5lint version ${getVersion()}`);
log.verbose(`using ui5lint version ${getFormattedVersion()}`);
log.verbose(`using node version ${process.version}`);
}
}
7 changes: 6 additions & 1 deletion src/cli/version.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
let version: string;
let formattedVersion: string;

// This module holds the CLI's version information (set via cli.js) for later retrieval (e.g. from middlewares/logger)
export function setVersion(v: string) {
export function setVersionInfo(v: string, p: string) {
version = v;
formattedVersion = `${v} (from ${p})`;
}
export function getVersion(): string {
return version || "";
}
export function getFormattedVersion(): string {
return formattedVersion || "";
}
18 changes: 12 additions & 6 deletions src/formatter/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {LintResult, LintMessage} from "../linter/LinterContext.js";
import {LintMessageSeverity} from "../linter/messages.js";

export class Markdown {
format(lintResults: LintResult[], showDetails: boolean): string {
format(lintResults: LintResult[], showDetails: boolean, version: string): string {
let totalErrorCount = 0;
let totalWarningCount = 0;
let totalFatalErrorCount = 0;
Expand All @@ -21,11 +21,11 @@ export class Markdown {
// Add the file path as a section header
findings += `### ${filePath}\n\n`;
if (showDetails === true) {
findings += `| Severity | Line | Message | Details |\n`;
findings += `|----------|------|---------|---------|\n`;
findings += `| Severity | Rule | Location | Message | Details |\n`;
findings += `|----------|------|----------|---------|---------|\n`;
} else {
findings += `| Severity | Line | Message |\n`;
findings += `|----------|------|---------|\n`;
findings += `| Severity | Rule | Location | Message |\n`;
findings += `|----------|------|----------|---------|\n`;
}

// Sort messages by severity (sorting order: fatal-errors, errors, warnings)
Expand All @@ -50,14 +50,15 @@ export class Markdown {
messages.forEach((msg) => {
const severity = this.formatSeverity(msg.severity, msg.fatal);
const location = this.formatLocation(msg.line, msg.column);
const rule = this.formatRuleId(msg.ruleId, version);
let details;
if (showDetails) {
details = ` ${this.formatMessageDetails(msg)} |`;
} else {
details = "";
}

findings += `| ${severity} | \`${location}\` | ${msg.message} |${details}\n`;
findings += `| ${severity} | ${rule} | \`${location}\` | ${msg.message} |${details}\n`;
});

findings += "\n";
Expand Down Expand Up @@ -113,4 +114,9 @@ ${findings}`;
// Replace multiple spaces or newlines with a single space for clean output
return `${msg.messageDetails.replace(/\s\s+|\n/g, " ")}`;
}

// Formats the rule of the lint message (ruleId and link to rules.md)
private formatRuleId(ruleId: string, version: string): string {
return `[${ruleId}](https://github.com/SAP/ui5-linter/blob/v${version}/docs/Rules.md#${ruleId})`;
}
}
24 changes: 15 additions & 9 deletions test/lib/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ const test = anyTest as TestFn<{
argv: () => unknown;
};
yargs: SinonStub;
setVersion: SinonStub;
setVersionInfo: SinonStub;
getFormattedVersion: SinonStub;
cliBase: SinonStub;
readdir: SinonStub;
cli: MockFunction;
Expand All @@ -44,13 +45,15 @@ test.beforeEach(async (t) => {

t.context.yargs = sinon.stub().returns(t.context.yargsInstance).named("yargs");

t.context.setVersion = sinon.stub().named("setVersion");
t.context.setVersionInfo = sinon.stub().named("setVersionInfo");
t.context.getFormattedVersion = sinon.stub().returns("1.2.3 (from /path/to/cli.js)").named("getFormattedVersion");
t.context.cliBase = sinon.stub().named("cliBase");

t.context.cli = await esmock.p("../../src/cli.js", {
"yargs": t.context.yargs,
"../../src/cli/version.js": {
setVersion: t.context.setVersion,
setVersionInfo: t.context.setVersionInfo,
getFormattedVersion: t.context.getFormattedVersion,
},
"../../src/cli/base.js": t.context.cliBase,
"module": {
Expand All @@ -68,7 +71,7 @@ test.afterEach.always((t) => {
test.serial("CLI", async (t) => {
const {
cli, argvGetter, yargsInstance, yargs,
setVersion, cliBase,
setVersionInfo, cliBase, getFormattedVersion,
} = t.context;

await cli("module");
Expand All @@ -81,14 +84,17 @@ test.serial("CLI", async (t) => {
"parse-numbers": false,
}]);

t.is(setVersion.callCount, 1);
t.deepEqual(setVersion.getCall(0).args, [
`${pkg.version} (from ${fileURLToPath(new URL("../../bin/ui5lint.js", import.meta.url))})`,
t.is(setVersionInfo.callCount, 1);
t.deepEqual(setVersionInfo.getCall(0).args, [
pkg.version, fileURLToPath(new URL("../../bin/ui5lint.js", import.meta.url)),
]);

t.is(getFormattedVersion.callCount, 1);
t.deepEqual(getFormattedVersion.getCall(0).args, []);

t.is(yargsInstance.version.callCount, 1);
t.deepEqual(yargsInstance.version.getCall(0).args, [
`${pkg.version} (from ${fileURLToPath(new URL("../../bin/ui5lint.js", import.meta.url))})`,
getFormattedVersion.getCall(0).returnValue,
]);

t.is(yargsInstance.scriptName.callCount, 1);
Expand All @@ -111,7 +117,7 @@ test.serial("CLI", async (t) => {
sinon.assert.callOrder(
yargs,
yargsInstance.parserConfiguration,
setVersion,
setVersionInfo,
yargsInstance.version,
yargsInstance.scriptName,
cliBase,
Expand Down
10 changes: 5 additions & 5 deletions test/lib/cli/middlewares/logger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const test = anyTest as TestFn<{
verboseLogStub: SinonStub;
setLogLevelStub: SinonStub;
isLogLevelEnabledStub: SinonStub;
getVersionStub: SinonStub;
getFormattedVersionStub: SinonStub;
logger: MockFunction & {
initLogger: (args:
{loglevel?: string; verbose?: boolean; perf?: boolean; silent?: boolean}) => Promise<void> | void;
Expand All @@ -17,10 +17,10 @@ test.beforeEach(async (t) => {
t.context.verboseLogStub = sinon.stub();
t.context.setLogLevelStub = sinon.stub();
t.context.isLogLevelEnabledStub = sinon.stub().returns(true);
t.context.getVersionStub = sinon.stub().returns("1.0.0");
t.context.getFormattedVersionStub = sinon.stub().returns("1.0.0");
t.context.logger = await esmock("../../../../src/cli/middlewares/logger.js", {
"../../../../src/cli/version.js": {
getVersion: t.context.getVersionStub,
getFormattedVersion: t.context.getFormattedVersionStub,
},
"@ui5/logger": {
getLogger: () => ({
Expand All @@ -33,13 +33,13 @@ test.beforeEach(async (t) => {
});

test.serial("init logger", async (t) => {
const {logger, setLogLevelStub, isLogLevelEnabledStub, verboseLogStub, getVersionStub} = t.context;
const {logger, setLogLevelStub, isLogLevelEnabledStub, verboseLogStub, getFormattedVersionStub} = t.context;
await logger.initLogger({});
t.is(setLogLevelStub.callCount, 0, "setLevel has not been called");
t.is(isLogLevelEnabledStub.callCount, 1, "isLogLevelEnabled has been called once");
t.is(isLogLevelEnabledStub.firstCall.firstArg, "verbose",
"isLogLevelEnabled has been called with expected argument");
t.is(getVersionStub.callCount, 1, "getVersion has been called once");
t.is(getFormattedVersionStub.callCount, 1, "getFormattedVersion has been called once");
t.is(verboseLogStub.callCount, 2, "log.verbose has been called twice");
t.is(verboseLogStub.firstCall.firstArg, "using ui5lint version 1.0.0",
"log.verbose has been called with expected argument on first call");
Expand Down
17 changes: 12 additions & 5 deletions test/lib/cli/version.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import test from "ava";
import {setVersion, getVersion} from "../../../src/cli/version.js";
import {setVersionInfo, getFormattedVersion, getVersion} from "../../../src/cli/version.js";

test("Set and get version", (t) => {
const sampleVersion = "1.2.3";
const sampleVersion2 = "4.5.6-foo.bar";
const samplePath = "/path/to/cli.js";

t.is(getFormattedVersion(), "");
t.is(getVersion(), "");

setVersion("1.2.3");
t.is(getVersion(), "1.2.3");
setVersionInfo(sampleVersion, samplePath);
t.is(getFormattedVersion(), `${sampleVersion} (from ${samplePath})`);
t.is(getVersion(), sampleVersion);

setVersion("4.5.6-foo.bar");
t.is(getVersion(), "4.5.6-foo.bar");
setVersionInfo(sampleVersion2, samplePath);
t.is(getFormattedVersion(), `${sampleVersion2} (from ${samplePath})`);
t.is(getVersion(), sampleVersion2);
});
Loading
Loading