@salesforce-ux/slds-linter 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/build/commands/emit.d.ts +2 -0
  2. package/build/commands/emit.js +48 -0
  3. package/build/commands/lint.d.ts +2 -0
  4. package/build/commands/lint.js +55 -0
  5. package/build/commands/report.d.ts +2 -0
  6. package/build/commands/report.js +66 -0
  7. package/build/executor/__tests__/executor.test.js +216 -0
  8. package/build/executor/index.d.ts +20 -0
  9. package/build/executor/index.js +105 -0
  10. package/build/index.d.ts +2 -0
  11. package/build/index.js +33 -0
  12. package/build/services/__tests__/file-scanner.test.js +47 -0
  13. package/build/services/artifact-processor.d.ts +6 -0
  14. package/build/services/artifact-processor.js +37 -0
  15. package/build/services/batch-processor.d.ts +29 -0
  16. package/build/services/batch-processor.js +84 -0
  17. package/build/services/config.resolver.d.ts +6 -0
  18. package/build/services/config.resolver.js +20 -0
  19. package/build/services/file-patterns.d.ts +3 -0
  20. package/build/services/file-patterns.js +21 -0
  21. package/build/services/file-scanner.d.ts +26 -0
  22. package/build/services/file-scanner.js +71 -0
  23. package/build/services/lint-runner.d.ts +17 -0
  24. package/build/services/lint-runner.js +69 -0
  25. package/build/services/report-generator.d.ts +43 -0
  26. package/build/services/report-generator.js +186 -0
  27. package/build/types/index.d.ts +75 -0
  28. package/build/types/index.js +0 -0
  29. package/build/utils/config-utils.d.ts +33 -0
  30. package/build/utils/config-utils.js +68 -0
  31. package/build/utils/editorLinkUtil.d.ts +21 -0
  32. package/build/utils/editorLinkUtil.js +21 -0
  33. package/build/utils/index.d.ts +2 -0
  34. package/build/utils/index.js +7 -0
  35. package/build/utils/lintResultsUtil.d.ts +21 -0
  36. package/build/utils/lintResultsUtil.js +70 -0
  37. package/build/utils/logger.d.ts +8 -0
  38. package/build/utils/logger.js +28 -0
  39. package/build/utils/nodeVersionUtil.d.ts +19 -0
  40. package/build/utils/nodeVersionUtil.js +42 -0
  41. package/build/workers/base.worker.d.ts +15 -0
  42. package/build/workers/base.worker.js +44 -0
  43. package/build/workers/eslint.worker.d.ts +1 -0
  44. package/build/workers/eslint.worker.js +50 -0
  45. package/build/workers/stylelint.worker.d.ts +1 -0
  46. package/build/workers/stylelint.worker.js +40 -0
  47. package/package.json +4 -4
@@ -0,0 +1,33 @@
1
+ import { LintConfig, ReportConfig, CliOptions } from '../types';
2
+ /**
3
+ * Normalize and validate a file path
4
+ * This function ensures that the provided path exists and is accessible
5
+ * If no path is provided, it defaults to the current working directory
6
+ *
7
+ * @param inputPath Input file path or undefined
8
+ * @returns Normalized and validated absolute path
9
+ * @throws Error if the path is invalid or inaccessible
10
+ */
11
+ export declare function normalizeAndValidatePath(inputPath?: string): string;
12
+ /**
13
+ * Normalize directory path with special handling for glob patterns
14
+ * This function preserves glob patterns and resolves regular paths to absolute paths
15
+ * If no directory is provided, it defaults to the current working directory
16
+ *
17
+ * @param directory Input directory or glob pattern or undefined
18
+ * @returns Normalized directory path
19
+ */
20
+ export declare function normalizeDirectoryPath(directory?: string): string;
21
+ /**
22
+ * Normalize configuration with appropriate defaults
23
+ * This function ensures all required options have valid values
24
+ * It applies provided defaults, then user-provided options, then normalizes paths
25
+ * Used by both CLI commands and Node API functions
26
+ *
27
+ * @param options Options to normalize
28
+ * @param defaultOptions Default options to apply
29
+ * @param isNodeApi Whether this is being called from the Node API
30
+ * @returns Normalized options with appropriate defaults
31
+ */
32
+ export declare function normalizeCliOptions<T extends CliOptions | LintConfig | ReportConfig>(options: T, defaultOptions?: Partial<T>, isNodeApi?: boolean): T;
33
+ export declare const normalizeConfig: typeof normalizeCliOptions;
@@ -0,0 +1,68 @@
1
+ // src/utils/config-utils.ts
2
+ import path from "path";
3
+ import { DEFAULT_ESLINT_CONFIG_PATH, DEFAULT_STYLELINT_CONFIG_PATH } from "../services/config.resolver.js";
4
+ import { isDynamicPattern } from "globby";
5
+ import { accessSync } from "fs";
6
+ import { Logger } from "./logger.js";
7
+ function normalizeAndValidatePath(inputPath) {
8
+ if (!inputPath) {
9
+ Logger.debug("No path provided, using current working directory");
10
+ return process.cwd();
11
+ }
12
+ const normalizedPath = path.resolve(inputPath);
13
+ Logger.debug(`Normalized path: ${normalizedPath}`);
14
+ try {
15
+ accessSync(normalizedPath);
16
+ return normalizedPath;
17
+ } catch (error) {
18
+ const errorMessage = `Invalid path: ${inputPath}`;
19
+ Logger.error(errorMessage);
20
+ throw new Error(errorMessage);
21
+ }
22
+ }
23
+ function normalizeDirectoryPath(directory) {
24
+ if (!directory) {
25
+ Logger.debug("No directory provided, using current working directory");
26
+ return process.cwd();
27
+ }
28
+ if (isDynamicPattern(directory)) {
29
+ Logger.debug(`Detected glob pattern: ${directory}`);
30
+ return directory;
31
+ }
32
+ Logger.debug(`Normalizing directory path: ${directory}`);
33
+ return path.resolve(directory);
34
+ }
35
+ function normalizeCliOptions(options, defaultOptions = {}, isNodeApi = false) {
36
+ const baseDefaults = {
37
+ files: [],
38
+ configStylelint: isNodeApi ? DEFAULT_STYLELINT_CONFIG_PATH : "",
39
+ configEslint: isNodeApi ? DEFAULT_ESLINT_CONFIG_PATH : ""
40
+ };
41
+ if (!isNodeApi) {
42
+ Object.assign(baseDefaults, {
43
+ fix: false,
44
+ editor: "vscode",
45
+ format: "sarif"
46
+ });
47
+ }
48
+ const normalizedOptions = {
49
+ ...baseDefaults,
50
+ ...defaultOptions,
51
+ ...options,
52
+ directory: normalizeDirectoryPath(options.directory)
53
+ };
54
+ if (!isNodeApi) {
55
+ normalizedOptions.output = options.output ? normalizeAndValidatePath(options.output) : process.cwd();
56
+ }
57
+ if ("format" in options && !options.format && isNodeApi) {
58
+ normalizedOptions.format = "sarif";
59
+ }
60
+ return normalizedOptions;
61
+ }
62
+ var normalizeConfig = normalizeCliOptions;
63
+ export {
64
+ normalizeAndValidatePath,
65
+ normalizeCliOptions,
66
+ normalizeConfig,
67
+ normalizeDirectoryPath
68
+ };
@@ -0,0 +1,21 @@
1
+ /**
2
+ * Returns an editor-specific link for opening a file at a given line and column.
3
+ *
4
+ * @param editor - The editor to use (e.g., 'vscode', 'atom', 'sublime').
5
+ * @param absolutePath - The absolute path to the file.
6
+ * @param line - The line number in the file.
7
+ * @param column - The column number in the file.
8
+ * @returns A URL string that can be used to open the file in the specified editor.
9
+ */
10
+ export declare function getEditorLink(editor: string, absolutePath: string, line: number, column: number): string;
11
+ /**
12
+ * Creates an ANSI hyperlink (if supported) for the line:column text.
13
+ *
14
+ * @param lineCol - The line:column string (e.g., "10:5").
15
+ * @param absolutePath - The absolute path to the file.
16
+ * @param line - The line number in the file.
17
+ * @param column - The column number in the file.
18
+ * @param editor - The editor to use (e.g., 'vscode', 'atom', 'sublime').
19
+ * @returns A string with ANSI escape sequences to create a clickable hyperlink.
20
+ */
21
+ export declare function createClickableLineCol(lineCol: string, absolutePath: string, line: number, column: number, editor: string): string;
@@ -0,0 +1,21 @@
1
+ // src/utils/editorLinkUtil.ts
2
+ import chalk from "chalk";
3
+ function getEditorLink(editor, absolutePath, line, column) {
4
+ if (editor === "vscode") {
5
+ return `vscode://file/${absolutePath}:${line}:${column}`;
6
+ } else if (editor === "atom") {
7
+ return `atom://core/open/file?filename=${absolutePath}&line=${line}&column=${column}`;
8
+ } else if (editor === "sublime") {
9
+ return `file://${absolutePath}:${line}:${column}`;
10
+ } else {
11
+ return `file://${absolutePath}:${line}:${column}`;
12
+ }
13
+ }
14
+ function createClickableLineCol(lineCol, absolutePath, line, column, editor) {
15
+ const link = getEditorLink(editor, absolutePath, line, column);
16
+ return `\x1B]8;;${link}\x07${chalk.blueBright(lineCol)}\x1B]8;;\x07`;
17
+ }
18
+ export {
19
+ createClickableLineCol,
20
+ getEditorLink
21
+ };
@@ -0,0 +1,2 @@
1
+ export { printLintResults } from './lintResultsUtil';
2
+ export { Logger } from './logger';
@@ -0,0 +1,7 @@
1
+ // src/utils/index.ts
2
+ import { printLintResults } from "./lintResultsUtil.js";
3
+ import { Logger } from "./logger.js";
4
+ export {
5
+ Logger,
6
+ printLintResults
7
+ };
@@ -0,0 +1,21 @@
1
+ import { LintResult, LintResultEntry, SarifResultEntry } from '../types';
2
+ /**
3
+ *
4
+ * @param id - Rule id
5
+ * @returns updated Rule id without the namespace @salesforce-ux
6
+ */
7
+ export declare function replaceNamespaceinRules(id: string): string;
8
+ /**
9
+ *
10
+ * @param text - The input text that could either be a plain string or a stringified JSON object.
11
+ * @returns The parsed message or the original string if parsing fails.
12
+ */
13
+ export declare function parseText(text: string): string;
14
+ /**
15
+ * Prints detailed lint results for each file that has issues.
16
+ *
17
+ * @param results - Array of lint results.
18
+ * @param editor - The chosen editor for clickable links (e.g., "vscode", "atom", "sublime").
19
+ */
20
+ export declare function printLintResults(results: LintResult[], editor: string): void;
21
+ export declare function transformedResults(lintResult: LintResult, entry: LintResultEntry, level: 'error' | 'warning'): SarifResultEntry;
@@ -0,0 +1,70 @@
1
+ // src/utils/lintResultsUtil.ts
2
+ import chalk from "chalk";
3
+ import path from "path";
4
+ import { createClickableLineCol } from "./editorLinkUtil.js";
5
+ import { Logger } from "../utils/logger.js";
6
+ function replaceNamespaceinRules(id) {
7
+ return id.includes("@salesforce-ux/") ? id.replace("@salesforce-ux/", "") : id;
8
+ }
9
+ function parseText(text) {
10
+ let safeText = text;
11
+ try {
12
+ const parsed = JSON.parse(text);
13
+ safeText = parsed.message || JSON.stringify(parsed);
14
+ } catch (error) {
15
+ safeText = text;
16
+ }
17
+ return safeText.endsWith(".") ? safeText : `${safeText}.`;
18
+ }
19
+ function printLintResults(results, editor) {
20
+ results.forEach((result) => {
21
+ const hasErrors = result.errors && result.errors.length > 0;
22
+ const hasWarnings = result.warnings && result.warnings.length > 0;
23
+ if (!hasErrors && !hasWarnings) return;
24
+ const absolutePath = result.filePath || "";
25
+ const relativeFile = path.relative(process.cwd(), absolutePath) || "Unknown file";
26
+ Logger.newLine().info(`${chalk.bold(relativeFile)}`);
27
+ if (hasErrors) {
28
+ result.errors.forEach((error) => {
29
+ if (error.line && error.column && absolutePath) {
30
+ const lineCol = `${error.line}:${error.column}`;
31
+ const clickable = createClickableLineCol(lineCol, absolutePath, error.line, error.column, editor);
32
+ const ruleId = error.ruleId ? chalk.dim(replaceNamespaceinRules(error.ruleId)) : "";
33
+ Logger.error(` ${clickable} ${parseText(error.message)} ${ruleId}`);
34
+ } else {
35
+ Logger.error(` ${chalk.red("Error:")} ${parseText(error.message)}`);
36
+ }
37
+ });
38
+ }
39
+ if (hasWarnings) {
40
+ result.warnings.forEach((warn) => {
41
+ if (warn.line && warn.column && absolutePath) {
42
+ const lineCol = `${warn.line}:${warn.column}`;
43
+ const clickable = createClickableLineCol(lineCol, absolutePath, warn.line, warn.column, editor);
44
+ const ruleId = warn.ruleId ? chalk.dim(replaceNamespaceinRules(warn.ruleId)) : "";
45
+ Logger.warning(` ${clickable} ${parseText(warn.message)} ${ruleId}`);
46
+ } else {
47
+ Logger.warning(` ${chalk.yellow("Warning:")} ${parseText(warn.message)}`);
48
+ }
49
+ });
50
+ }
51
+ });
52
+ }
53
+ function transformedResults(lintResult, entry, level) {
54
+ return {
55
+ ruleId: replaceNamespaceinRules(entry.ruleId),
56
+ level,
57
+ messageText: parseText(entry.message),
58
+ fileUri: path.relative(process.cwd(), lintResult.filePath),
59
+ startLine: entry.line,
60
+ startColumn: entry.column,
61
+ endLine: entry.line,
62
+ endColumn: entry.endColumn
63
+ };
64
+ }
65
+ export {
66
+ parseText,
67
+ printLintResults,
68
+ replaceNamespaceinRules,
69
+ transformedResults
70
+ };
@@ -0,0 +1,8 @@
1
+ export declare class Logger {
2
+ static newLine(): typeof Logger;
3
+ static info(message: string): void;
4
+ static success(message: string): void;
5
+ static warning(message: string): void;
6
+ static error(message: string): void;
7
+ static debug(message: string): void;
8
+ }
@@ -0,0 +1,28 @@
1
+ // src/utils/logger.ts
2
+ import chalk from "chalk";
3
+ var Logger = class {
4
+ static newLine() {
5
+ console.log("\n");
6
+ return this;
7
+ }
8
+ static info(message) {
9
+ console.log(chalk.blue("\u2139"), message);
10
+ }
11
+ static success(message) {
12
+ console.log(chalk.green("\u2713"), message);
13
+ }
14
+ static warning(message) {
15
+ console.warn(chalk.yellow("\u26A0"), message);
16
+ }
17
+ static error(message) {
18
+ console.error(chalk.red("\u2716"), message);
19
+ }
20
+ static debug(message) {
21
+ if (process.env.DEBUG) {
22
+ console.debug(chalk.gray("\u{1F50D}"), message);
23
+ }
24
+ }
25
+ };
26
+ export {
27
+ Logger
28
+ };
@@ -0,0 +1,19 @@
1
+ export declare const REQUIRED_NODE_VERSION = ">=18.4.0";
2
+ /**
3
+ * Checks if the current Node.js version meets the required version.
4
+ * @param {string} requiredVersion - The required Node.js version.
5
+ * @returns {boolean} - Returns true if the current version is valid.
6
+ */
7
+ export declare function checkNodeVersion(requiredVersion: any): boolean;
8
+ /**
9
+ * Validates the Node.js version and exits if it does not meet the requirement.
10
+ */
11
+ export declare function validateNodeVersion(): void;
12
+ /**
13
+ * Util to resolve dirName from import meta compatible with node v18
14
+ * Letst version of node have import.meta.dirname
15
+ * @param importMeta
16
+ * @returns
17
+ */
18
+ export declare function resolveDirName(importMeta: ImportMeta): string;
19
+ export declare function resolvePath(specifier: string, importMeta: ImportMeta): string;
@@ -0,0 +1,42 @@
1
+ // src/utils/nodeVersionUtil.ts
2
+ import semver from "semver";
3
+ import { fileURLToPath } from "url";
4
+ import { dirname } from "path";
5
+ import { resolve } from "import-meta-resolve";
6
+ import { Logger } from "./logger.js";
7
+ var REQUIRED_NODE_VERSION = ">=18.4.0";
8
+ function checkNodeVersion(requiredVersion) {
9
+ return semver.satisfies(process.version, requiredVersion);
10
+ }
11
+ function validateNodeVersion() {
12
+ if (!checkNodeVersion(REQUIRED_NODE_VERSION)) {
13
+ if (checkNodeVersion("<18.4.x")) {
14
+ Logger.warning(
15
+ `SLDS Linter CLI works best with Node.js version v18.4.0 or later.
16
+ We recommend using the latest [Active LTS](https://nodejs.org/en/about/previous-releases) version of Node.js.`
17
+ );
18
+ }
19
+ }
20
+ }
21
+ function resolveDirName(importMeta) {
22
+ if ("dirname" in importMeta) {
23
+ return importMeta.dirname;
24
+ }
25
+ return dirname(fileURLToPath(importMeta.url));
26
+ }
27
+ function resolvePath(specifier, importMeta) {
28
+ let fileUrl = "";
29
+ if ("resolve" in importMeta) {
30
+ fileUrl = importMeta.resolve(specifier);
31
+ } else {
32
+ fileUrl = resolve(specifier, importMeta.url);
33
+ }
34
+ return fileURLToPath(fileUrl);
35
+ }
36
+ export {
37
+ REQUIRED_NODE_VERSION,
38
+ checkNodeVersion,
39
+ resolveDirName,
40
+ resolvePath,
41
+ validateNodeVersion
42
+ };
@@ -0,0 +1,15 @@
1
+ import { BatchTask } from '../services/batch-processor';
2
+ export declare abstract class BaseWorker<T, R> {
3
+ protected task: BatchTask<T>;
4
+ constructor();
5
+ /**
6
+ * Process a single file
7
+ * @param filePath Path to the file to process
8
+ * @returns Processing result
9
+ */
10
+ protected abstract processFile(filePath: string): Promise<R>;
11
+ /**
12
+ * Start processing the batch of files
13
+ */
14
+ process(): Promise<void>;
15
+ }
@@ -0,0 +1,44 @@
1
+ // src/workers/base.worker.ts
2
+ import { parentPort, workerData } from "worker_threads";
3
+ var BaseWorker = class {
4
+ task;
5
+ constructor() {
6
+ this.task = workerData;
7
+ }
8
+ /**
9
+ * Start processing the batch of files
10
+ */
11
+ async process() {
12
+ try {
13
+ const results = [];
14
+ for (const file of this.task.files) {
15
+ try {
16
+ const result = await this.processFile(file);
17
+ results.push(result);
18
+ } catch (error) {
19
+ results.push({
20
+ file,
21
+ error: error.message
22
+ });
23
+ }
24
+ }
25
+ const batchResult = {
26
+ success: true,
27
+ results
28
+ };
29
+ parentPort?.postMessage(batchResult);
30
+ } catch (error) {
31
+ const errorResult = {
32
+ success: false,
33
+ error: error.message,
34
+ results: []
35
+ };
36
+ parentPort?.postMessage(errorResult);
37
+ } finally {
38
+ process.exit(0);
39
+ }
40
+ }
41
+ };
42
+ export {
43
+ BaseWorker
44
+ };
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,50 @@
1
+ // src/workers/eslint.worker.ts
2
+ import { ESLint } from "eslint";
3
+ import { BaseWorker } from "./base.worker.js";
4
+ var ESLintWorker = class extends BaseWorker {
5
+ eslint;
6
+ constructor() {
7
+ super();
8
+ this.eslint = new ESLint({
9
+ useEslintrc: false,
10
+ fix: this.task.config.fix,
11
+ overrideConfigFile: this.task.config.configPath
12
+ });
13
+ }
14
+ async processFile(filePath) {
15
+ try {
16
+ const results = await this.eslint.lintFiles([filePath]);
17
+ const fileResult = results[0];
18
+ if (this.task.config.fix && fileResult.output) {
19
+ await ESLint.outputFixes(results);
20
+ }
21
+ return {
22
+ file: filePath,
23
+ warnings: fileResult.messages.filter((msg) => msg.severity === 1).map((warning) => ({
24
+ line: warning.line,
25
+ column: warning.column,
26
+ endColumn: warning.endColumn,
27
+ message: warning.message,
28
+ ruleId: warning.ruleId || "unknown"
29
+ })),
30
+ errors: fileResult.messages.filter((msg) => msg.severity === 2).map((error) => ({
31
+ line: error.line,
32
+ column: error.column,
33
+ endColumn: error.endColumn,
34
+ message: error.message,
35
+ ruleId: error.ruleId || "unknown"
36
+ }))
37
+ };
38
+ } catch (error) {
39
+ return {
40
+ file: filePath,
41
+ error: error.message
42
+ };
43
+ }
44
+ }
45
+ };
46
+ var worker = new ESLintWorker();
47
+ worker.process().catch((error) => {
48
+ console.error("Worker failed:", error);
49
+ process.exit(1);
50
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,40 @@
1
+ // src/workers/stylelint.worker.ts
2
+ import stylelint from "stylelint";
3
+ import { BaseWorker } from "./base.worker.js";
4
+ var StylelintWorker = class extends BaseWorker {
5
+ async processFile(filePath) {
6
+ try {
7
+ const options = {
8
+ files: filePath,
9
+ fix: this.task.config.fix
10
+ };
11
+ if (this.task.config.configPath) {
12
+ options.configFile = this.task.config.configPath;
13
+ }
14
+ const result = await stylelint.lint(options);
15
+ const fileResult = result.results[0];
16
+ return {
17
+ file: filePath,
18
+ warnings: fileResult.warnings.map((warning) => ({
19
+ line: warning.line,
20
+ column: warning.column,
21
+ endColumn: warning.endColumn,
22
+ message: warning.text,
23
+ ruleId: warning.rule
24
+ })),
25
+ errors: []
26
+ // Stylelint doesn't differentiate between warnings and errors
27
+ };
28
+ } catch (error) {
29
+ return {
30
+ file: filePath,
31
+ error: error.message
32
+ };
33
+ }
34
+ }
35
+ };
36
+ var worker = new StylelintWorker();
37
+ worker.process().catch((error) => {
38
+ console.error("Worker failed:", error);
39
+ process.exit(1);
40
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce-ux/slds-linter",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "SLDS Linter CLI tool for linting styles and components",
5
5
  "keywords": [
6
6
  "lightning design system linter",
@@ -27,10 +27,10 @@
27
27
  "build/**",
28
28
  "README.md"
29
29
  ],
30
- "bin": "./build/index.js",
30
+ "bin": "build/index.js",
31
31
  "dependencies": {
32
- "@salesforce-ux/eslint-plugin-slds": "0.2.0",
33
- "@salesforce-ux/stylelint-plugin-slds": "0.2.0",
32
+ "@salesforce-ux/eslint-plugin-slds": "0.2.1",
33
+ "@salesforce-ux/stylelint-plugin-slds": "0.2.1",
34
34
  "@typescript-eslint/eslint-plugin": "^5.0.0",
35
35
  "@typescript-eslint/parser": "^5.0.0",
36
36
  "chalk": "^4.1.2",