@utoo/lint 0.3.7 → 0.3.9

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
@@ -244,6 +244,20 @@ const results = await eslint.lintFiles(["src"]);
244
244
  await ESLint.outputFixes(results);
245
245
  ```
246
246
 
247
+ `await eslint.loadFormatter()` and `cliEngine.getFormatter()` default to an
248
+ ESLint-style `stylish` report: aligned columns, blank lines between files,
249
+ diagnostics sorted by line and column, and a summary of fixable errors and
250
+ warnings. Rule messages (including punctuation), result arrays, and exit codes
251
+ are unchanged. Only actual autofixes count toward the `--fix` hint, not editor
252
+ suggestions; missing JSX keys still need a manually chosen stable, unique key.
253
+
254
+ The stylish formatter detects color support on stdout. Pass `color: true` or
255
+ `color: false` to the `ESLint` or `CLIEngine` constructor to override detection.
256
+ Otherwise, `NO_COLOR` or `NODE_DISABLE_COLORS` disables color; `FORCE_COLOR`
257
+ forces it (`0` disables), followed by `CLICOLOR_FORCE` and stdout TTY detection
258
+ (`TERM=dumb` disables automatic color). Constructor `env` overrides also apply.
259
+ JSON, JSON-with-metadata, compact, unix, and native CLI text formats are unchanged.
260
+
247
261
  Raw reports include active diagnostics, suppressed diagnostics, fixed outputs,
248
262
  and an exit code. The compatibility layer also exports `Linter`, `CLIEngine`,
249
263
  `RuleTester`, and `SourceCode` APIs.
package/index.cjs CHANGED
@@ -6,6 +6,7 @@ const { dirname, extname, isAbsolute, join, relative, resolve: resolvePath } = r
6
6
 
7
7
  const { platformPackageName, resolveBinary } = require("./lib/binary.cjs");
8
8
  const { createLegacyConfigResolver } = require("./lib/legacy-config.cjs");
9
+ const { formatESLintResults } = require("./lib/stylish-formatter.cjs");
9
10
  const {
10
11
  findConfigPath: findConfigPathFromDirectory,
11
12
  readConfig
@@ -396,6 +397,10 @@ const BUILTIN_RULE_IDS = [
396
397
  "react/void-dom-elements-no-children",
397
398
  "react-hooks/exhaustive-deps",
398
399
  "react-hooks/rules-of-hooks",
400
+ "react-hooks/use-memo",
401
+ "react-hooks/void-use-memo",
402
+ "react-hooks/set-state-in-render",
403
+ "react-hooks/purity",
399
404
  "unused-imports/no-unused-imports",
400
405
  "@typescript-eslint/adjacent-overload-signatures",
401
406
  "@typescript-eslint/array-type",
@@ -626,7 +631,7 @@ class UtooLint {
626
631
  format(results) {
627
632
  return formatResultsByName(results, name, {
628
633
  rulesMeta: (results) => eslint.getRulesMetaForResults(results)
629
- });
634
+ }, eslint.options);
630
635
  }
631
636
  };
632
637
  }
@@ -704,7 +709,7 @@ class CLIEngine {
704
709
 
705
710
  getFormatter(name = "stylish") {
706
711
  return (results) => {
707
- return formatResultsByName(results, name);
712
+ return formatResultsByName(results, name, {}, this.options);
708
713
  };
709
714
  }
710
715
 
@@ -5004,31 +5009,7 @@ function maybeFilterQuietResults(results, options = {}) {
5004
5009
  return results;
5005
5010
  }
5006
5011
 
5007
- function formatESLintResults(results) {
5008
- const lines = [];
5009
- let errorCount = 0;
5010
- let warningCount = 0;
5011
-
5012
- for (const result of results) {
5013
- if (result.messages.length === 0) continue;
5014
- lines.push(result.filePath);
5015
- for (const message of result.messages) {
5016
- const severity = message.severity === 2 ? "error" : "warning";
5017
- lines.push(` ${message.line}:${message.column} ${severity} ${message.message} ${message.ruleId}`);
5018
- }
5019
- errorCount += result.errorCount;
5020
- warningCount += result.warningCount;
5021
- }
5022
-
5023
- if (errorCount || warningCount) {
5024
- lines.push("");
5025
- lines.push(`x ${errorCount + warningCount} problem${errorCount + warningCount === 1 ? "" : "s"} (${errorCount} error${errorCount === 1 ? "" : "s"}, ${warningCount} warning${warningCount === 1 ? "" : "s"})`);
5026
- }
5027
-
5028
- return lines.join("\n");
5029
- }
5030
-
5031
- function formatResultsByName(input, name = "stylish", metadata = {}) {
5012
+ function formatResultsByName(input, name = "stylish", metadata = {}, options = {}) {
5032
5013
  const results = formatterResults(input);
5033
5014
  if (name === "json") {
5034
5015
  return JSON.stringify(input);
@@ -5047,7 +5028,7 @@ function formatResultsByName(input, name = "stylish", metadata = {}) {
5047
5028
  if (name === "unix") {
5048
5029
  return formatUnixResults(results);
5049
5030
  }
5050
- return formatESLintResults(results);
5031
+ return formatESLintResults(results, options);
5051
5032
  }
5052
5033
 
5053
5034
  function formatterResults(input) {
package/index.js CHANGED
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";
7
7
 
8
8
  import { resolveBinary } from "./lib/binary.js";
9
9
  import { createLegacyConfigResolver } from "./lib/legacy-config.cjs";
10
+ import { formatESLintResults } from "./lib/stylish-formatter.cjs";
10
11
  import {
11
12
  findConfigPath as findConfigPathFromDirectory,
12
13
  readConfig
@@ -399,6 +400,10 @@ const BUILTIN_RULE_IDS = [
399
400
  "react/void-dom-elements-no-children",
400
401
  "react-hooks/exhaustive-deps",
401
402
  "react-hooks/rules-of-hooks",
403
+ "react-hooks/use-memo",
404
+ "react-hooks/void-use-memo",
405
+ "react-hooks/set-state-in-render",
406
+ "react-hooks/purity",
402
407
  "unused-imports/no-unused-imports",
403
408
  "@typescript-eslint/adjacent-overload-signatures",
404
409
  "@typescript-eslint/array-type",
@@ -629,7 +634,7 @@ export class UtooLint {
629
634
  format(results) {
630
635
  return formatResultsByName(results, name, {
631
636
  rulesMeta: (results) => eslint.getRulesMetaForResults(results)
632
- });
637
+ }, eslint.options);
633
638
  }
634
639
  };
635
640
  }
@@ -2603,7 +2608,7 @@ export class CLIEngine {
2603
2608
 
2604
2609
  getFormatter(name = "stylish") {
2605
2610
  return (results) => {
2606
- return formatResultsByName(results, name);
2611
+ return formatResultsByName(results, name, {}, this.options);
2607
2612
  };
2608
2613
  }
2609
2614
 
@@ -5007,31 +5012,7 @@ function maybeFilterQuietResults(results, options = {}) {
5007
5012
  return results;
5008
5013
  }
5009
5014
 
5010
- function formatESLintResults(results) {
5011
- const lines = [];
5012
- let errorCount = 0;
5013
- let warningCount = 0;
5014
-
5015
- for (const result of results) {
5016
- if (result.messages.length === 0) continue;
5017
- lines.push(result.filePath);
5018
- for (const message of result.messages) {
5019
- const severity = message.severity === 2 ? "error" : "warning";
5020
- lines.push(` ${message.line}:${message.column} ${severity} ${message.message} ${message.ruleId}`);
5021
- }
5022
- errorCount += result.errorCount;
5023
- warningCount += result.warningCount;
5024
- }
5025
-
5026
- if (errorCount || warningCount) {
5027
- lines.push("");
5028
- lines.push(`x ${errorCount + warningCount} problem${errorCount + warningCount === 1 ? "" : "s"} (${errorCount} error${errorCount === 1 ? "" : "s"}, ${warningCount} warning${warningCount === 1 ? "" : "s"})`);
5029
- }
5030
-
5031
- return lines.join("\n");
5032
- }
5033
-
5034
- function formatResultsByName(input, name = "stylish", metadata = {}) {
5015
+ function formatResultsByName(input, name = "stylish", metadata = {}, options = {}) {
5035
5016
  const results = formatterResults(input);
5036
5017
  if (name === "json") {
5037
5018
  return JSON.stringify(input);
@@ -5050,7 +5031,7 @@ function formatResultsByName(input, name = "stylish", metadata = {}) {
5050
5031
  if (name === "unix") {
5051
5032
  return formatUnixResults(results);
5052
5033
  }
5053
- return formatESLintResults(results);
5034
+ return formatESLintResults(results, options);
5054
5035
  }
5055
5036
 
5056
5037
  function formatterResults(input) {
@@ -0,0 +1,79 @@
1
+ const { stripVTControlCharacters } = require("node:util");
2
+
3
+ function formatESLintResults(results, options = {}) {
4
+ const color = shouldUseColor(options);
5
+ const paint = (text, open, close) => color ? `\u001b[${open}m${text}\u001b[${close}m` : text;
6
+ const lines = [""];
7
+ let errorCount = 0;
8
+ let warningCount = 0;
9
+ let fixableErrorCount = 0;
10
+ let fixableWarningCount = 0;
11
+
12
+ for (const result of results) {
13
+ if (result.messages.length === 0) continue;
14
+ lines.push(paint(result.filePath, 4, 24));
15
+ // Sort a copy: formatting must not reorder the caller's results or JSON output.
16
+ const messages = [...result.messages].sort((left, right) =>
17
+ (left.line ?? 0) - (right.line ?? 0) || (left.column ?? 0) - (right.column ?? 0)
18
+ );
19
+ const rows = messages.map((message) => [
20
+ String(message.line || 0),
21
+ String(message.column || 0),
22
+ message.fatal || message.severity === 2 ? "error" : "warning",
23
+ // Unlike ESLint stylish, retain punctuation in the original rule message.
24
+ message.message,
25
+ message.ruleId || "",
26
+ ]);
27
+ const widths = [0, 0, 0, 0];
28
+ for (const row of rows) {
29
+ for (let index = 0; index < widths.length; index += 1) {
30
+ widths[index] = Math.max(widths[index], stripVTControlCharacters(row[index]).length);
31
+ }
32
+ }
33
+ for (const [line, column, severity, message, ruleId] of rows) {
34
+ const location = " ".repeat(widths[0] - line.length)
35
+ + paint(`${line}:${column}`, 2, 22)
36
+ + " ".repeat(widths[1] - column.length);
37
+ const label = paint(severity, severity === "error" ? 31 : 33, 39)
38
+ + " ".repeat(widths[2] - severity.length);
39
+ const padding = " ".repeat(widths[3] - stripVTControlCharacters(message).length);
40
+ const description = ruleId ? `${message}${padding} ${paint(ruleId, 2, 22)}` : message;
41
+ lines.push(` ${location} ${label} ${description}`);
42
+ }
43
+ lines.push("");
44
+ errorCount += result.errorCount;
45
+ warningCount += result.warningCount;
46
+ fixableErrorCount += result.fixableErrorCount ?? 0;
47
+ fixableWarningCount += result.fixableWarningCount ?? 0;
48
+ }
49
+
50
+ if (errorCount || warningCount) {
51
+ const summary = (text) => paint(paint(text, 1, 22), errorCount ? 31 : 33, 39);
52
+ lines.push(summary(
53
+ `✖ ${pluralize(errorCount + warningCount, "problem")} (${pluralize(errorCount, "error")}, ${pluralize(warningCount, "warning")})`
54
+ ));
55
+ if (fixableErrorCount || fixableWarningCount) {
56
+ lines.push(summary(
57
+ ` ${pluralize(fixableErrorCount, "error")} and ${pluralize(fixableWarningCount, "warning")} potentially fixable with the \`--fix\` option.`
58
+ ));
59
+ }
60
+ lines.push("");
61
+ }
62
+
63
+ return lines.join("\n");
64
+ }
65
+
66
+ function pluralize(count, noun) {
67
+ return `${count} ${noun}${count === 1 ? "" : "s"}`;
68
+ }
69
+
70
+ function shouldUseColor(options) {
71
+ if (typeof options.color === "boolean") return options.color;
72
+ const env = options.env ? { ...process.env, ...options.env } : process.env;
73
+ if (env.NO_COLOR !== undefined || env.NODE_DISABLE_COLORS !== undefined) return false;
74
+ if (env.FORCE_COLOR !== undefined) return env.FORCE_COLOR !== "0";
75
+ if (env.CLICOLOR_FORCE && env.CLICOLOR_FORCE !== "0") return true;
76
+ return env.TERM !== "dumb" && Boolean(process.stdout.isTTY);
77
+ }
78
+
79
+ module.exports = { formatESLintResults };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@utoo/lint",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "High-performance JavaScript and TypeScript linter powered by Yuku",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -76,11 +76,11 @@
76
76
  "use-at-your-own-risk.js"
77
77
  ],
78
78
  "optionalDependencies": {
79
- "@utoo/lint-darwin-arm64": "0.3.7",
80
- "@utoo/lint-darwin-x64": "0.3.7",
81
- "@utoo/lint-linux-arm64": "0.3.7",
82
- "@utoo/lint-linux-x64": "0.3.7",
83
- "@utoo/lint-win32-x64": "0.3.7"
79
+ "@utoo/lint-darwin-arm64": "0.3.9",
80
+ "@utoo/lint-darwin-x64": "0.3.9",
81
+ "@utoo/lint-linux-arm64": "0.3.9",
82
+ "@utoo/lint-linux-x64": "0.3.9",
83
+ "@utoo/lint-win32-x64": "0.3.9"
84
84
  },
85
85
  "dependencies": {
86
86
  "@eslint/eslintrc": "2.1.4",