@howells/lint 0.2.4 → 0.2.6

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
@@ -69,7 +69,7 @@ Node or non-React TypeScript package:
69
69
 
70
70
  ```json
71
71
  {
72
- "$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
72
+ "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
73
73
  "extends": ["@howells/lint/biome/core"],
74
74
  "root": true
75
75
  }
@@ -79,7 +79,7 @@ React package:
79
79
 
80
80
  ```json
81
81
  {
82
- "$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
82
+ "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
83
83
  "extends": ["@howells/lint/biome/core", "@howells/lint/biome/react"],
84
84
  "root": true
85
85
  }
@@ -89,12 +89,8 @@ Next.js app:
89
89
 
90
90
  ```json
91
91
  {
92
- "$schema": "https://biomejs.dev/schemas/2.4.15/schema.json",
93
- "extends": [
94
- "@howells/lint/biome/core",
95
- "@howells/lint/biome/react",
96
- "@howells/lint/biome/next"
97
- ],
92
+ "$schema": "https://biomejs.dev/schemas/2.4.16/schema.json",
93
+ "extends": ["@howells/lint/biome/core", "@howells/lint/biome/react", "@howells/lint/biome/next"],
98
94
  "root": true
99
95
  }
100
96
  ```
@@ -103,7 +99,9 @@ Next.js app:
103
99
 
104
100
  Use this lane when a project wants Oxlint and Oxfmt instead of Biome. React and Next presets stack the relevant Ultracite Ox rules with [React Doctor](https://react.doctor) rules in one config.
105
101
 
106
- React Doctor severities are passed through as published by React Doctor. Native Oxlint Next.js severities come from Oxlint's official `nextjs` plugin via Ultracite's Next preset. `@howells/lint` adds canonical Howells policy on top for file naming, barrel files, env access, and tests.
102
+ React Doctor severities are passed through as published by React Doctor. Native Oxlint Next.js severities come from Oxlint's official `nextjs` plugin via Ultracite's Next preset. `@howells/lint` adds canonical Howells policy on top for file naming, barrel files, env access, file size, function size, complexity, and tests.
103
+
104
+ The core Oxlint preset enables native Oxlint rules that keep code files navigable: `max-lines` errors above 600 non-comment, non-blank lines; `max-lines-per-function` errors above 120 non-comment, non-blank lines; `max-statements` errors above 45 statements per function; and `complexity` errors above cyclomatic complexity 15. Test files keep the file-level `max-lines` guard but disable function-size, statement-count, and complexity limits, because test framework callbacks naturally wrap many independent cases. Generated files should be ignored at the project level; rare intentional exceptions should use an exact-file override with a short refactor note.
107
105
 
108
106
  Choose the closest preset:
109
107
 
package/bin/env.mjs ADDED
@@ -0,0 +1,3 @@
1
+ /* oxlint-disable no-restricted-properties -- CLI wrappers intentionally pass the caller environment to child processes. */
2
+
3
+ export const inheritedEnv = () => process.env;
@@ -3,9 +3,6 @@
3
3
  import { runPackageBin } from "./run-package-bin.mjs";
4
4
 
5
5
  const targets = process.argv.slice(2);
6
- const args =
7
- targets.length > 0
8
- ? ["check", "--write", ...targets]
9
- : ["check", "--write", "."];
6
+ const args = targets.length > 0 ? ["check", "--write", ...targets] : ["check", "--write", "."];
10
7
 
11
8
  runPackageBin("@biomejs/biome", "biome", args);
@@ -4,39 +4,38 @@ import { runPackageBin } from "./run-package-bin.mjs";
4
4
 
5
5
  const args = process.argv.slice(2);
6
6
  const passthroughOptions = new Set(["--help", "-h", "--version", "-V"]);
7
- const targets =
8
- args.length === 0 || args[0].startsWith("-") ? [".", ...args] : args;
7
+ const targets = args.length === 0 || args[0].startsWith("-") ? [".", ...args] : args;
9
8
 
10
9
  const strictRuleOptions = [
11
- "--only=security",
12
- "--skip=security/noSecrets",
13
- "--only=correctness/noConstAssign",
14
- "--only=correctness/noUnreachable",
15
- "--only=correctness/noInvalidConstructorSuper",
16
- "--only=correctness/noSetterReturn",
17
- "--only=correctness/noUnsafeFinally",
18
- "--only=correctness/noUnsafeOptionalChaining",
19
- "--only=correctness/noGlobalObjectCalls",
20
- "--only=correctness/noSelfAssign",
21
- "--only=correctness/noSwitchDeclarations",
22
- "--only=suspicious/noDebugger",
23
- "--only=suspicious/noDoubleEquals",
24
- "--only=suspicious/noExplicitAny",
25
- "--only=suspicious/noCatchAssign",
26
- "--only=suspicious/noFunctionAssign",
27
- "--only=suspicious/noGlobalAssign",
28
- "--only=suspicious/noRedeclare",
29
- "--only=suspicious/noSparseArray",
30
- "--only=suspicious/noVar",
31
- "--only=suspicious/noDuplicateCase",
32
- "--only=suspicious/noDuplicateObjectKeys",
33
- "--only=suspicious/noDuplicateParameters",
34
- "--only=suspicious/noFallthroughSwitchClause",
35
- "--only=suspicious/noFocusedTests",
10
+ "--only=security",
11
+ "--skip=security/noSecrets",
12
+ "--only=correctness/noConstAssign",
13
+ "--only=correctness/noUnreachable",
14
+ "--only=correctness/noInvalidConstructorSuper",
15
+ "--only=correctness/noSetterReturn",
16
+ "--only=correctness/noUnsafeFinally",
17
+ "--only=correctness/noUnsafeOptionalChaining",
18
+ "--only=correctness/noGlobalObjectCalls",
19
+ "--only=correctness/noSelfAssign",
20
+ "--only=correctness/noSwitchDeclarations",
21
+ "--only=suspicious/noDebugger",
22
+ "--only=suspicious/noDoubleEquals",
23
+ "--only=suspicious/noExplicitAny",
24
+ "--only=suspicious/noCatchAssign",
25
+ "--only=suspicious/noFunctionAssign",
26
+ "--only=suspicious/noGlobalAssign",
27
+ "--only=suspicious/noRedeclare",
28
+ "--only=suspicious/noSparseArray",
29
+ "--only=suspicious/noVar",
30
+ "--only=suspicious/noDuplicateCase",
31
+ "--only=suspicious/noDuplicateObjectKeys",
32
+ "--only=suspicious/noDuplicateParameters",
33
+ "--only=suspicious/noFallthroughSwitchClause",
34
+ "--only=suspicious/noFocusedTests",
36
35
  ];
37
36
 
38
37
  const resolvedArgs = passthroughOptions.has(args[0])
39
- ? args
40
- : ["lint", ...strictRuleOptions, ...targets];
38
+ ? args
39
+ : ["lint", ...strictRuleOptions, ...targets];
41
40
 
42
41
  runPackageBin("@biomejs/biome", "biome", resolvedArgs);
@@ -4,33 +4,41 @@ import { runPackageBin } from "./run-package-bin.mjs";
4
4
 
5
5
  const args = process.argv.slice(2);
6
6
  const biomeCommands = new Set([
7
- "version",
8
- "rage",
9
- "start",
10
- "stop",
11
- "check",
12
- "lint",
13
- "format",
14
- "ci",
15
- "init",
16
- "migrate",
17
- "search",
18
- "explain",
19
- "clean",
20
- "daemon",
21
- "lsp-proxy",
7
+ "version",
8
+ "rage",
9
+ "start",
10
+ "stop",
11
+ "check",
12
+ "lint",
13
+ "format",
14
+ "ci",
15
+ "init",
16
+ "migrate",
17
+ "search",
18
+ "explain",
19
+ "clean",
20
+ "daemon",
21
+ "lsp-proxy",
22
22
  ]);
23
23
  const passthroughOptions = new Set(["--help", "-h", "--version", "-V"]);
24
24
 
25
- const resolvedArgs =
26
- args.length === 0
27
- ? ["check", "."]
28
- : biomeCommands.has(args[0])
29
- ? args
30
- : passthroughOptions.has(args[0])
31
- ? args
32
- : args[0].startsWith("-")
33
- ? ["check", ".", ...args]
34
- : ["check", ...args];
25
+ const resolveBiomeArgs = (inputArgs) => {
26
+ if (inputArgs.length === 0) {
27
+ return ["check", "."];
28
+ }
29
+
30
+ const [firstArg] = inputArgs;
31
+ if (biomeCommands.has(firstArg) || passthroughOptions.has(firstArg)) {
32
+ return inputArgs;
33
+ }
34
+
35
+ if (firstArg.startsWith("-")) {
36
+ return ["check", ".", ...inputArgs];
37
+ }
38
+
39
+ return ["check", ...inputArgs];
40
+ };
41
+
42
+ const resolvedArgs = resolveBiomeArgs(args);
35
43
 
36
44
  runPackageBin("@biomejs/biome", "biome", resolvedArgs);
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawnSync } from "node:child_process";
4
+ import { inheritedEnv } from "./env.mjs";
4
5
  import { resolvePackageBin } from "./run-package-bin.mjs";
5
6
 
6
7
  const args = process.argv.slice(2);
@@ -8,21 +9,21 @@ const targets = args.filter((arg) => !arg.startsWith("-"));
8
9
  const oxlintOptions = args.filter((arg) => arg.startsWith("-"));
9
10
  const resolvedTargets = targets.length > 0 ? targets : ["."];
10
11
 
11
- function run(packageName, binName, commandArgs) {
12
- const binPath = resolvePackageBin(packageName, binName);
13
- const result = spawnSync(binPath, commandArgs, {
14
- stdio: "inherit",
15
- env: process.env,
16
- });
12
+ const run = (packageName, binName, commandArgs) => {
13
+ const binPath = resolvePackageBin(packageName, binName);
14
+ const result = spawnSync(binPath, commandArgs, {
15
+ env: inheritedEnv(),
16
+ stdio: "inherit",
17
+ });
17
18
 
18
- if (result.error) {
19
- throw result.error;
20
- }
19
+ if (result.error) {
20
+ throw result.error;
21
+ }
21
22
 
22
- if ((result.status ?? 1) !== 0) {
23
- process.exit(result.status ?? 1);
24
- }
25
- }
23
+ if ((result.status ?? 1) !== 0) {
24
+ process.exit(result.status ?? 1);
25
+ }
26
+ };
26
27
 
27
28
  run("oxfmt", "oxfmt", ["--check", ...resolvedTargets]);
28
29
  run("oxlint", "oxlint", [...oxlintOptions, ...resolvedTargets]);
@@ -1,6 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  import { spawnSync } from "node:child_process";
4
+ import { inheritedEnv } from "./env.mjs";
4
5
  import { resolvePackageBin } from "./run-package-bin.mjs";
5
6
 
6
7
  const args = process.argv.slice(2);
@@ -10,25 +11,25 @@ const targets = filteredArgs.filter((arg) => !arg.startsWith("-"));
10
11
  const oxlintOptions = filteredArgs.filter((arg) => arg.startsWith("-"));
11
12
  const resolvedTargets = targets.length > 0 ? targets : ["."];
12
13
 
13
- function run(packageName, binName, commandArgs) {
14
- const binPath = resolvePackageBin(packageName, binName);
15
- const result = spawnSync(binPath, commandArgs, {
16
- stdio: "inherit",
17
- env: process.env,
18
- });
14
+ const run = (packageName, binName, commandArgs) => {
15
+ const binPath = resolvePackageBin(packageName, binName);
16
+ const result = spawnSync(binPath, commandArgs, {
17
+ env: inheritedEnv(),
18
+ stdio: "inherit",
19
+ });
19
20
 
20
- if (result.error) {
21
- throw result.error;
22
- }
21
+ if (result.error) {
22
+ throw result.error;
23
+ }
23
24
 
24
- if ((result.status ?? 1) !== 0) {
25
- process.exit(result.status ?? 1);
26
- }
27
- }
25
+ if ((result.status ?? 1) !== 0) {
26
+ process.exit(result.status ?? 1);
27
+ }
28
+ };
28
29
 
29
30
  run("oxfmt", "oxfmt", ["--write", ...resolvedTargets]);
30
31
  run("oxlint", "oxlint", [
31
- useDangerousFixes ? "--fix-dangerously" : "--fix",
32
- ...oxlintOptions,
33
- ...resolvedTargets,
32
+ useDangerousFixes ? "--fix-dangerously" : "--fix",
33
+ ...oxlintOptions,
34
+ ...resolvedTargets,
34
35
  ]);
@@ -7,19 +7,19 @@ import { runWorkspacePreflight } from "./workspace-preflight.mjs";
7
7
  const args = process.argv.slice(2);
8
8
 
9
9
  if (handleManypkgMetadataCommand("check", args)) {
10
- process.exit(0);
10
+ process.exit(0);
11
11
  }
12
12
 
13
13
  const errors = runWorkspacePreflight();
14
14
 
15
15
  if (errors.length > 0) {
16
- console.error("Workspace hygiene check failed:");
16
+ console.error("Workspace hygiene check failed:");
17
17
 
18
- for (const error of errors) {
19
- console.error(`- ${error}`);
20
- }
18
+ for (const error of errors) {
19
+ console.error(`- ${error}`);
20
+ }
21
21
 
22
- process.exit(1);
22
+ process.exit(1);
23
23
  }
24
24
 
25
25
  runPackageBin("@manypkg/cli", "manypkg", ["check", ...args]);
@@ -7,34 +7,34 @@ const require = createRequire(import.meta.url);
7
7
  const helpOptions = new Set(["--help", "-h"]);
8
8
  const versionOptions = new Set(["--version", "-V"]);
9
9
 
10
- export function printManypkgCommandHelp(command) {
11
- console.log(`Usage: howells-workspace-${command} [options]\n`);
12
- console.log(`Runs: manypkg ${command} [options]`);
13
- }
14
-
15
- export function printManypkgCliVersion() {
16
- const { version } = require("@manypkg/cli/package.json");
17
- console.log(version);
18
- }
19
-
20
- export function handleManypkgMetadataCommand(command, args) {
21
- if (helpOptions.has(args[0])) {
22
- printManypkgCommandHelp(command);
23
- return true;
24
- }
25
-
26
- if (versionOptions.has(args[0])) {
27
- printManypkgCliVersion();
28
- return true;
29
- }
30
-
31
- return false;
32
- }
33
-
34
- export function runManypkgCommand(command, args) {
35
- if (handleManypkgMetadataCommand(command, args)) {
36
- process.exit(0);
37
- }
38
-
39
- runPackageBin("@manypkg/cli", "manypkg", [command, ...args]);
40
- }
10
+ export const printManypkgCommandHelp = (command) => {
11
+ console.log(`Usage: howells-workspace-${command} [options]\n`);
12
+ console.log(`Runs: manypkg ${command} [options]`);
13
+ };
14
+
15
+ export const printManypkgCliVersion = () => {
16
+ const { version } = require("@manypkg/cli/package.json");
17
+ console.log(version);
18
+ };
19
+
20
+ export const handleManypkgMetadataCommand = (command, args) => {
21
+ if (helpOptions.has(args[0])) {
22
+ printManypkgCommandHelp(command);
23
+ return true;
24
+ }
25
+
26
+ if (versionOptions.has(args[0])) {
27
+ printManypkgCliVersion();
28
+ return true;
29
+ }
30
+
31
+ return false;
32
+ };
33
+
34
+ export const runManypkgCommand = (command, args) => {
35
+ if (handleManypkgMetadataCommand(command, args)) {
36
+ process.exit(0);
37
+ }
38
+
39
+ runPackageBin("@manypkg/cli", "manypkg", [command, ...args]);
40
+ };
@@ -4,75 +4,65 @@ import { spawnSync } from "node:child_process";
4
4
  import { existsSync, readFileSync } from "node:fs";
5
5
  import { createRequire } from "node:module";
6
6
  import { dirname, join } from "node:path";
7
- import { fileURLToPath } from "node:url";
7
+ import { inheritedEnv } from "./env.mjs";
8
8
 
9
9
  const require = createRequire(import.meta.url);
10
- const currentDir = dirname(fileURLToPath(import.meta.url));
11
-
12
- function resolvePackageJsonPath(packageName) {
13
- try {
14
- return require.resolve(`${packageName}/package.json`);
15
- } catch {
16
- const packageSegments = packageName.split("/");
17
- let searchDir = currentDir;
18
-
19
- while (true) {
20
- const candidate = join(
21
- searchDir,
22
- "..",
23
- "node_modules",
24
- ...packageSegments,
25
- "package.json",
26
- );
27
-
28
- if (existsSync(candidate)) {
29
- return candidate;
30
- }
31
-
32
- const parentDir = dirname(searchDir);
33
-
34
- if (parentDir === searchDir) {
35
- break;
36
- }
37
-
38
- searchDir = parentDir;
39
- }
40
- }
41
-
42
- throw new Error(
43
- `Could not resolve package.json for package '${packageName}'.`,
44
- );
45
- }
46
-
47
- export function resolvePackageBin(packageName, binName) {
48
- const packageJsonPath = resolvePackageJsonPath(packageName);
49
- const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
50
- const packageDir = dirname(packageJsonPath);
51
- const binField = packageJson.bin;
52
-
53
- if (typeof binField === "string") {
54
- return join(packageDir, binField);
55
- }
56
-
57
- if (binField && typeof binField === "object" && binField[binName]) {
58
- return join(packageDir, binField[binName]);
59
- }
60
-
61
- throw new Error(
62
- `Could not resolve bin '${binName}' for package '${packageName}'.`,
63
- );
64
- }
65
-
66
- export function runPackageBin(packageName, binName, args) {
67
- const binPath = resolvePackageBin(packageName, binName);
68
- const result = spawnSync(binPath, args, {
69
- stdio: "inherit",
70
- env: process.env,
71
- });
72
-
73
- if (result.error) {
74
- throw result.error;
75
- }
76
-
77
- process.exit(result.status ?? 1);
78
- }
10
+ const currentDir = import.meta.dirname;
11
+
12
+ const resolvePackageJsonPath = (packageName) => {
13
+ try {
14
+ return require.resolve(`${packageName}/package.json`);
15
+ } catch {
16
+ const packageSegments = packageName.split("/");
17
+ let searchDir = currentDir;
18
+
19
+ while (true) {
20
+ const candidate = join(searchDir, "..", "node_modules", ...packageSegments, "package.json");
21
+
22
+ if (existsSync(candidate)) {
23
+ return candidate;
24
+ }
25
+
26
+ const parentDir = dirname(searchDir);
27
+
28
+ if (parentDir === searchDir) {
29
+ break;
30
+ }
31
+
32
+ searchDir = parentDir;
33
+ }
34
+ }
35
+
36
+ throw new Error(`Could not resolve package.json for package '${packageName}'.`);
37
+ };
38
+
39
+ export const resolvePackageBin = (packageName, binName) => {
40
+ const packageJsonPath = resolvePackageJsonPath(packageName);
41
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
42
+ const packageDir = dirname(packageJsonPath);
43
+ const binField = packageJson.bin;
44
+
45
+ if (typeof binField === "string") {
46
+ return join(packageDir, binField);
47
+ }
48
+
49
+ if (binField && typeof binField === "object" && binField[binName]) {
50
+ return join(packageDir, binField[binName]);
51
+ }
52
+
53
+ throw new Error(`Could not resolve bin '${binName}' for package '${packageName}'.`);
54
+ };
55
+
56
+ export const runPackageBin = (packageName, binName, args) => {
57
+ const binPath = resolvePackageBin(packageName, binName);
58
+ const result = spawnSync(binPath, args, {
59
+ env: inheritedEnv(),
60
+ stdio: "inherit",
61
+ });
62
+
63
+ if (result.error) {
64
+ throw result.error;
65
+ }
66
+
67
+ process.exit(result.status ?? 1);
68
+ };