@salesforce-ux/slds-linter 0.2.2 → 0.3.1-internal-beta.0

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.
package/README.md CHANGED
@@ -102,7 +102,7 @@ These options are available on SLDS Linter commands.
102
102
  | `--fix` | Automatically fix problems | `lint` |
103
103
  | `--config-stylelint <path>` | Path to stylelint config file | `lint`, `report`|
104
104
  | `--config-eslint <path>` | Path to eslint config file | `lint`, `report`|
105
- | `--editor <editor>` | Editor to open files with (e.g., vscode, atom, sublime). Defaults to vscode | `lint` |
105
+ | `--editor <editor>` | Editor to open files with (e.g., vscode, atom, sublime). Auto-detects if not specified | `lint` |
106
106
  | `--format <type>` | Output format (sarif, csv). Defaults to sarif | `report` |
107
107
 
108
108
  To view help for these options, add `--help` to each command. For example, run `npx @salesforce-ux/slds-linter lint --help` to see which options you can use with `lint`.
@@ -11,7 +11,7 @@ function registerLintCommand(program) {
11
11
  commandUsage: () => {
12
12
  return `${program.name()} lint [directory] [options]`;
13
13
  }
14
- }).description("Run both style and component linting").argument("[directory]", "Target directory to scan (defaults to current directory). Support glob patterns").addOption(new Option("-d, --directory <path>", "Target directory to scan (defaults to current directory). Support glob patterns").hideHelp()).option("--fix", "Automatically fix problems").option("--config-stylelint <path>", "Path to stylelint config file").option("--config-eslint <path>", "Path to eslint config file").option("--editor <editor>", "Editor to open files with (e.g., vscode, atom, sublime). Defaults to vscode", "vscode").action(async (directory, options) => {
14
+ }).description("Run both style and component linting").argument("[directory]", "Target directory to scan (defaults to current directory). Support glob patterns").addOption(new Option("-d, --directory <path>", "Target directory to scan (defaults to current directory). Support glob patterns").hideHelp()).option("--fix", "Automatically fix problems").option("--config-stylelint <path>", "Path to stylelint config file").option("--config-eslint <path>", "Path to eslint config file").option("--editor <editor>", "Editor to open files with (e.g., vscode, atom, sublime). Auto-detects if not specified").action(async (directory, options) => {
15
15
  const startTime = Date.now();
16
16
  try {
17
17
  Logger.info(chalk.blue("Starting lint process..."));
package/build/index.js CHANGED
@@ -19,7 +19,7 @@ process.on("uncaughtException", (error) => {
19
19
  var program = new Command();
20
20
  program.name("npx @salesforce-ux/slds-linter@latest").showHelpAfterError();
21
21
  function registerVersion() {
22
- program.description("SLDS Linter CLI tool for linting styles and components").version("0.2.2");
22
+ program.description("SLDS Linter CLI tool for linting styles and components").version("0.3.1-internal-beta.0");
23
23
  }
24
24
  registerLintCommand(program);
25
25
  registerReportCommand(program);
@@ -5,7 +5,7 @@ var DEFAULT_ESLINT_CONFIG_PATH = resolvePath("@salesforce-ux/eslint-plugin-slds/
5
5
  var DEFAULT_STYLELINT_CONFIG_PATH = resolvePath("@salesforce-ux/stylelint-plugin-slds/.stylelintrc.yml", import.meta);
6
6
  var STYLELINT_VERSION = "16.14.1";
7
7
  var ESLINT_VERSION = "8.57.1";
8
- var LINTER_CLI_VERSION = "0.2.2";
8
+ var LINTER_CLI_VERSION = "0.3.1-internal-beta.0";
9
9
  var getRuleDescription = (ruleId) => {
10
10
  const ruleIdWithoutNameSpace = `${ruleId}`.replace(/\@salesforce-ux\//, "");
11
11
  return ruleMetadata(ruleIdWithoutNameSpace)?.ruleDesc || "--";
@@ -4,6 +4,7 @@ import { DEFAULT_ESLINT_CONFIG_PATH, DEFAULT_STYLELINT_CONFIG_PATH } from "../se
4
4
  import { isDynamicPattern } from "globby";
5
5
  import { accessSync } from "fs";
6
6
  import { Logger } from "./logger.js";
7
+ import { detectCurrentEditor } from "./editorLinkUtil.js";
7
8
  function normalizeAndValidatePath(inputPath) {
8
9
  if (!inputPath) {
9
10
  Logger.debug("No path provided, using current working directory");
@@ -41,7 +42,7 @@ function normalizeCliOptions(options, defaultOptions = {}, isNodeApi = false) {
41
42
  if (!isNodeApi) {
42
43
  Object.assign(baseDefaults, {
43
44
  fix: false,
44
- editor: "vscode",
45
+ editor: detectCurrentEditor(),
45
46
  format: "sarif"
46
47
  });
47
48
  }
@@ -1,7 +1,13 @@
1
+ /**
2
+ * Auto-detects the current editor based on environment variables and common patterns.
3
+ *
4
+ * @returns The detected editor name or 'vscode' as fallback
5
+ */
6
+ export declare function detectCurrentEditor(): string;
1
7
  /**
2
8
  * Returns an editor-specific link for opening a file at a given line and column.
3
9
  *
4
- * @param editor - The editor to use (e.g., 'vscode', 'atom', 'sublime').
10
+ * @param editor - The editor to use (e.g., 'vscode').
5
11
  * @param absolutePath - The absolute path to the file.
6
12
  * @param line - The line number in the file.
7
13
  * @param column - The column number in the file.
@@ -15,7 +21,7 @@ export declare function getEditorLink(editor: string, absolutePath: string, line
15
21
  * @param absolutePath - The absolute path to the file.
16
22
  * @param line - The line number in the file.
17
23
  * @param column - The column number in the file.
18
- * @param editor - The editor to use (e.g., 'vscode', 'atom', 'sublime').
24
+ * @param editor - The editor to use (e.g., 'vscode', 'atom', 'sublime'). If not provided, will auto-detect.
19
25
  * @returns A string with ANSI escape sequences to create a clickable hyperlink.
20
26
  */
21
- export declare function createClickableLineCol(lineCol: string, absolutePath: string, line: number, column: number, editor: string): string;
27
+ export declare function createClickableLineCol(lineCol: string, absolutePath: string, line: number, column: number, editor?: string): string;
@@ -1,21 +1,52 @@
1
1
  // src/utils/editorLinkUtil.ts
2
2
  import chalk from "chalk";
3
+ function detectCurrentEditor() {
4
+ const editor = process.env.EDITOR || process.env.VISUAL;
5
+ if (editor) {
6
+ const editorName = editor.toLowerCase();
7
+ if (editorName.includes("code") || editorName.includes("vscode")) {
8
+ return "vscode";
9
+ } else if (editorName.includes("cursor")) {
10
+ return "cursor";
11
+ } else if (editorName.includes("atom")) {
12
+ return "atom";
13
+ } else if (editorName.includes("sublime") || editorName.includes("subl")) {
14
+ return "sublime";
15
+ } else if (editorName.includes("vim") || editorName.includes("nvim")) {
16
+ return "vim";
17
+ } else if (editorName.includes("emacs")) {
18
+ return "emacs";
19
+ } else if (editorName.includes("nano")) {
20
+ return "nano";
21
+ }
22
+ }
23
+ if (process.env.CURSOR_TRACE_ID || (process.env.TERM_PROGRAM || "").toLowerCase().includes("cursor")) {
24
+ return "cursor";
25
+ }
26
+ if (process.env.VSCODE_PID || (process.env.TERM_PROGRAM || "").toLowerCase().includes("vscode")) {
27
+ return "vscode";
28
+ }
29
+ return "vscode";
30
+ }
3
31
  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}`;
32
+ switch (editor.toLowerCase()) {
33
+ case "vscode":
34
+ return `vscode://file/${absolutePath}:${line}:${column}`;
35
+ case "cursor":
36
+ return `cursor://file/${absolutePath}:${line}:${column}`;
37
+ case "atom":
38
+ return `atom://core/open/file?filename=${absolutePath}&line=${line}&column=${column}`;
39
+ default:
40
+ return `file://${absolutePath}:${line}:${column}`;
12
41
  }
13
42
  }
14
43
  function createClickableLineCol(lineCol, absolutePath, line, column, editor) {
15
- const link = getEditorLink(editor, absolutePath, line, column);
44
+ const detectedEditor = editor || detectCurrentEditor();
45
+ const link = getEditorLink(detectedEditor, absolutePath, line, column);
16
46
  return `\x1B]8;;${link}\x07${chalk.blueBright(lineCol)}\x1B]8;;\x07`;
17
47
  }
18
48
  export {
19
49
  createClickableLineCol,
50
+ detectCurrentEditor,
20
51
  getEditorLink
21
52
  };
@@ -15,7 +15,7 @@ export declare function parseText(text: string): string;
15
15
  * Prints detailed lint results for each file that has issues.
16
16
  *
17
17
  * @param results - Array of lint results.
18
- * @param editor - The chosen editor for clickable links (e.g., "vscode", "atom", "sublime").
18
+ * @param editor - The chosen editor for clickable links (e.g., "vscode", "atom", "sublime"). If not provided, will auto-detect.
19
19
  */
20
- export declare function printLintResults(results: LintResult[], editor: string): void;
20
+ export declare function printLintResults(results: LintResult[], editor?: string): void;
21
21
  export declare function transformedResults(lintResult: LintResult, entry: LintResultEntry, level: 'error' | 'warning'): SarifResultEntry;
@@ -21,7 +21,7 @@ var StylelintWorker = class extends BaseWorker {
21
21
  endColumn: warning.endColumn,
22
22
  message: warning.text,
23
23
  ruleId: warning.rule
24
- })),
24
+ })).sort((a, b) => a.line - b.line),
25
25
  errors: []
26
26
  // Stylelint doesn't differentiate between warnings and errors
27
27
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce-ux/slds-linter",
3
- "version": "0.2.2",
3
+ "version": "0.3.1-internal-beta.0",
4
4
  "description": "SLDS Linter CLI tool for linting styles and components",
5
5
  "keywords": [
6
6
  "lightning design system linter",
@@ -29,13 +29,13 @@
29
29
  ],
30
30
  "bin": "build/index.js",
31
31
  "dependencies": {
32
- "@salesforce-ux/eslint-plugin-slds": "0.2.2",
33
- "@salesforce-ux/stylelint-plugin-slds": "0.2.2",
32
+ "@salesforce-ux/eslint-plugin-slds": "0.3.1-internal-beta.0",
33
+ "@salesforce-ux/stylelint-plugin-slds": "0.3.1-internal-beta.0",
34
34
  "@typescript-eslint/eslint-plugin": "^5.0.0",
35
35
  "@typescript-eslint/parser": "^5.0.0",
36
36
  "chalk": "^4.1.2",
37
37
  "commander": "^13.1.0",
38
- "eslint": "^8.0.0",
38
+ "eslint": "8.57.1",
39
39
  "export-to-csv": "^1.4.0",
40
40
  "globby": "^14.1.0",
41
41
  "import-meta-resolve": "^4.1.0",
@@ -43,7 +43,7 @@
43
43
  "node-sarif-builder": "^3.2.0",
44
44
  "ora": "^5.4.1",
45
45
  "semver": "^7.7.1",
46
- "stylelint": "^16.10.0"
46
+ "stylelint": "16.14.1"
47
47
  },
48
48
  "license": "ISC",
49
49
  "repository": {
@@ -1,216 +0,0 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
- var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __hasOwnProp = Object.prototype.hasOwnProperty;
5
- var __esm = (fn, res) => function __init() {
6
- return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
7
- };
8
- var __export = (target, all) => {
9
- for (var name in all)
10
- __defProp(target, name, { get: all[name], enumerable: true });
11
- };
12
- var __copyProps = (to, from, except, desc) => {
13
- if (from && typeof from === "object" || typeof from === "function") {
14
- for (let key of __getOwnPropNames(from))
15
- if (!__hasOwnProp.call(to, key) && key !== except)
16
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
- }
18
- return to;
19
- };
20
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
21
-
22
- // src/executor/index.ts
23
- var executor_exports = {};
24
- __export(executor_exports, {
25
- lint: () => lint,
26
- report: () => report
27
- });
28
- import { Readable } from "stream";
29
- import { FileScanner } from "../services/file-scanner.js";
30
- import { LintRunner } from "../services/lint-runner.js";
31
- import { StyleFilePatterns, ComponentFilePatterns } from "../services/file-patterns.js";
32
- import { ReportGenerator, CsvReportGenerator } from "../services/report-generator.js";
33
- import { LINTER_CLI_VERSION } from "../services/config.resolver.js";
34
- import { normalizeCliOptions } from "../utils/config-utils.js";
35
- import { Logger } from "../utils/logger.js";
36
- async function lint(config) {
37
- try {
38
- Logger.debug("Starting linting with Node API");
39
- const normalizedConfig = normalizeCliOptions(config, {}, true);
40
- const styleFiles = await FileScanner.scanFiles(normalizedConfig.directory, {
41
- patterns: StyleFilePatterns,
42
- batchSize: 100
43
- });
44
- const componentFiles = await FileScanner.scanFiles(normalizedConfig.directory, {
45
- patterns: ComponentFilePatterns,
46
- batchSize: 100
47
- });
48
- const lintOptions = {
49
- fix: normalizedConfig.fix,
50
- configPath: normalizedConfig.configStylelint
51
- };
52
- const styleResults = await LintRunner.runLinting(styleFiles, "style", {
53
- ...lintOptions,
54
- configPath: normalizedConfig.configStylelint
55
- });
56
- const componentResults = await LintRunner.runLinting(componentFiles, "component", {
57
- ...lintOptions,
58
- configPath: normalizedConfig.configEslint
59
- });
60
- const combinedResults = [...styleResults, ...componentResults];
61
- return standardizeLintMessages(combinedResults);
62
- } catch (error) {
63
- const errorMessage = `Linting failed: ${error.message}`;
64
- Logger.error(errorMessage);
65
- throw new Error(errorMessage);
66
- }
67
- }
68
- async function report(config, results) {
69
- try {
70
- Logger.debug("Starting report generation with Node API");
71
- const normalizedConfig = normalizeCliOptions(config, {}, true);
72
- const format = normalizedConfig.format || "sarif";
73
- const lintResults = results || await lint({
74
- directory: normalizedConfig.directory,
75
- configStylelint: normalizedConfig.configStylelint,
76
- configEslint: normalizedConfig.configEslint
77
- });
78
- switch (format) {
79
- case "sarif":
80
- return ReportGenerator.generateSarifReportStream(lintResults, {
81
- toolName: "slds-linter",
82
- toolVersion: LINTER_CLI_VERSION
83
- });
84
- case "csv":
85
- const csvString = CsvReportGenerator.generateCsvString(lintResults);
86
- const csvStream = new Readable();
87
- csvStream.push(csvString);
88
- csvStream.push(null);
89
- return csvStream;
90
- default:
91
- const errorMessage = `Unsupported format: ${format}`;
92
- Logger.error(errorMessage);
93
- throw new Error(errorMessage);
94
- }
95
- } catch (error) {
96
- const errorMessage = `Report generation failed: ${error.message}`;
97
- Logger.error(errorMessage);
98
- throw new Error(errorMessage);
99
- }
100
- }
101
- function standardizeLintMessages(results) {
102
- return results.map((result) => ({
103
- ...result,
104
- errors: result.errors.map((entry) => {
105
- let msgObj;
106
- try {
107
- msgObj = JSON.parse(entry.message);
108
- if (typeof msgObj === "object" && "message" in msgObj) {
109
- return { ...entry, ...msgObj };
110
- }
111
- } catch {
112
- }
113
- return { ...entry, message: entry.message };
114
- }),
115
- warnings: result.warnings.map((entry) => {
116
- let msgObj;
117
- try {
118
- msgObj = JSON.parse(entry.message);
119
- if (typeof msgObj === "object" && "message" in msgObj) {
120
- return { ...entry, ...msgObj };
121
- }
122
- } catch {
123
- }
124
- return { ...entry, message: entry.message };
125
- })
126
- }));
127
- }
128
- var init_executor = __esm({
129
- "src/executor/index.ts"() {
130
- }
131
- });
132
-
133
- // src/executor/__tests__/executor.test.ts
134
- import { lint as lint2, report as report2 } from "../index.js";
135
- import { LintRunner as LintRunner2 } from "../../services/lint-runner.js";
136
- import { FileScanner as FileScanner2 } from "../../services/file-scanner.js";
137
- import { Readable as Readable2 } from "stream";
138
- import { jest } from "@jest/globals.js";
139
- var mockLintResult = {
140
- filePath: "file1.css",
141
- errors: [{ line: 1, column: 1, endColumn: 10, message: "Test error", ruleId: "test-rule", severity: 2 }],
142
- warnings: []
143
- };
144
- jest.mock("../../services/lint-runner", () => {
145
- return {
146
- LintRunner: {
147
- runLinting: jest.fn().mockImplementation(() => {
148
- return Promise.resolve([mockLintResult]);
149
- })
150
- }
151
- };
152
- });
153
- jest.mock("../../services/file-scanner", () => {
154
- return {
155
- FileScanner: {
156
- scanFiles: jest.fn().mockImplementation(() => {
157
- return Promise.resolve([["file1.css"]]);
158
- })
159
- }
160
- };
161
- });
162
- jest.mock("fs/promises");
163
- xdescribe("Executor functions", () => {
164
- beforeEach(() => {
165
- jest.clearAllMocks();
166
- });
167
- describe("lint", () => {
168
- it("should scan directory and run linting when no files are provided", async () => {
169
- const config = {
170
- directory: "./src",
171
- fix: true
172
- };
173
- const results = await lint2(config);
174
- expect(FileScanner2.scanFiles).toHaveBeenCalledTimes(2);
175
- expect(LintRunner2.runLinting).toHaveBeenCalledTimes(2);
176
- expect(results).toHaveLength(2);
177
- });
178
- it("should use provided files and skip scanning when files are provided", async () => {
179
- const config = {
180
- files: ["file1.css", "component1.html"]
181
- };
182
- await lint2(config);
183
- expect(FileScanner2.scanFiles).not.toHaveBeenCalled();
184
- expect(LintRunner2.runLinting).toHaveBeenCalledTimes(2);
185
- });
186
- });
187
- describe("report", () => {
188
- it("should return a readable stream", async () => {
189
- const config = {
190
- directory: "./src",
191
- format: "sarif"
192
- };
193
- const stream = await report2(config);
194
- expect(stream).toBeInstanceOf(Readable2);
195
- });
196
- it("should use lint results to generate a report", async () => {
197
- const lintMock = jest.spyOn((init_executor(), __toCommonJS(executor_exports)), "lint").mockResolvedValue([mockLintResult]);
198
- const config = {
199
- directory: "./src",
200
- format: "sarif"
201
- };
202
- await report2(config);
203
- expect(lintMock).toHaveBeenCalledWith({
204
- directory: "./src",
205
- configStylelint: expect.any(String),
206
- configEslint: expect.any(String)
207
- });
208
- lintMock.mockRestore();
209
- });
210
- });
211
- });
212
- describe("Executor placeholder tests", () => {
213
- it("should be implemented in the future", () => {
214
- expect(true).toBe(true);
215
- });
216
- });
@@ -1,47 +0,0 @@
1
- // src/services/__tests__/file-scanner.test.ts
2
- import path from "path";
3
- import { FileScanner } from "../file-scanner.js";
4
- import { StyleFilePatterns } from "../file-patterns.js";
5
- import { mkdir, writeFile, rm } from "fs/promises";
6
- import { fileURLToPath } from "url";
7
- var __filename = fileURLToPath(import.meta.url);
8
- var __dirname = path.dirname(__filename);
9
- describe("FileScanner", () => {
10
- const testDir = path.join(__dirname, "fixtures");
11
- beforeAll(async () => {
12
- await mkdir(testDir, { recursive: true });
13
- await writeFile(
14
- path.join(testDir, "test.css"),
15
- "body { color: red; }"
16
- );
17
- await writeFile(
18
- path.join(testDir, "test.scss"),
19
- "$color: red;"
20
- );
21
- });
22
- afterAll(async () => {
23
- await rm(testDir, { recursive: true });
24
- });
25
- it("should scan and batch files correctly", async () => {
26
- const options = {
27
- patterns: StyleFilePatterns,
28
- batchSize: 1
29
- };
30
- const batches = await FileScanner.scanFiles(testDir, options);
31
- expect(batches).toHaveLength(2);
32
- expect(batches[0]).toHaveLength(1);
33
- expect(batches[1]).toHaveLength(1);
34
- expect(batches[0][0]).toMatch(/test\.(css|scss)$/);
35
- expect(batches[1][0]).toMatch(/test\.(css|scss)$/);
36
- });
37
- it("should handle invalid files gracefully", async () => {
38
- const options = {
39
- patterns: {
40
- extensions: ["nonexistent"],
41
- exclude: []
42
- }
43
- };
44
- const batches = await FileScanner.scanFiles(testDir, options);
45
- expect(batches).toHaveLength(0);
46
- });
47
- });