@jterrazz/typescript 9.3.0 → 10.0.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 +20 -16
- package/bin/commands/check.sh +348 -117
- package/bin/typescript.sh +55 -0
- package/lib/check-architecture.js +89 -0
- package/lib/check-baseline.js +144 -0
- package/lib/check-docs.js +4 -3
- package/lib/check-drift.js +209 -0
- package/lib/check-gitignore.js +4 -4
- package/lib/check-markdown.js +279 -0
- package/lib/check-names.js +125 -0
- package/lib/check-publish.js +150 -0
- package/lib/check-secrets.js +115 -0
- package/lib/check-suppressions.js +355 -0
- package/lib/doctor.js +185 -0
- package/lib/merge-knip-config.js +57 -25
- package/lib/tracked-files.js +165 -0
- package/lib/workspace-members.js +5 -6
- package/package.json +19 -8
- package/presets/oxfmt/index.js +49 -5
- package/presets/oxlint/profiles/astro.js +10 -0
- package/presets/oxlint/profiles/bun.js +7 -0
- package/presets/oxlint/profiles/expo.js +7 -0
- package/presets/oxlint/profiles/library.js +16 -0
- package/presets/oxlint/profiles/next.js +7 -0
- package/presets/oxlint/profiles/node.js +7 -0
- package/presets/prettier/astro.json +6 -0
- package/presets/tsconfig/expo.json +16 -6
- package/presets/tsconfig/library.json +18 -0
- package/presets/tsconfig/next.json +12 -2
- package/presets/tsconfig/node.json +18 -4
- package/rules/README.md +23 -0
- package/rules/_contract.js +191 -0
- package/rules/_contract.test.ts +81 -0
- package/rules/a11y.js +51 -0
- package/rules/architecture/hexagonal.js +56 -0
- package/rules/architecture/layers.js +75 -0
- package/rules/astro.js +49 -0
- package/rules/catalog.js +134 -0
- package/rules/catalog.test.ts +84 -0
- package/rules/compile.js +125 -0
- package/rules/core/eslint.js +234 -0
- package/rules/core/import.js +107 -0
- package/rules/core/jsdoc.js +52 -0
- package/rules/core/node.js +36 -0
- package/rules/core/oxc.js +54 -0
- package/rules/core/promise.js +39 -0
- package/rules/core/typescript.js +204 -0
- package/rules/core/unicorn.js +200 -0
- package/rules/next.js +53 -0
- package/rules/profiles.js +89 -0
- package/rules/react-native.js +48 -0
- package/rules/react.js +148 -0
- package/rules/sorted.js +41 -0
- package/rules/vitest.js +153 -0
- package/src/docs.d.ts +4 -4
- package/src/docs.js +75 -57
- package/src/docs.test.ts +43 -32
- package/src/index.d.ts +13 -9
- package/src/index.js +15 -8
- package/src/oxfmt.d.ts +15 -2
- package/src/oxfmt.test.ts +10 -0
- package/src/oxlint.d.ts +57 -10
- package/src/oxlint.js +35 -50
- package/src/oxlint.test.ts +82 -28
- package/presets/oxlint/architectures/hexagonal-rules.js +0 -39
- package/presets/oxlint/architectures/hexagonal.js +0 -13
- package/presets/oxlint/base.js +0 -145
- package/presets/oxlint/expo.js +0 -36
- package/presets/oxlint/next.js +0 -43
- package/presets/oxlint/node.js +0 -14
- package/presets/oxlint/plugins/codestyle.js +0 -231
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files a project would carry into a commit — the one sweep every
|
|
3
|
+
* tree-reading gate starts from.
|
|
4
|
+
*
|
|
5
|
+
* `git ls-files --cached --others --exclude-standard` is the answer wherever
|
|
6
|
+
* there is a git tree: it names what is tracked AND what the next `git add`
|
|
7
|
+
* would track, so a file fails a gate before it is committed, not after. A
|
|
8
|
+
* directory git knows nothing about — a fixture the spec runner copied, a tree
|
|
9
|
+
* nobody has `git init`ed — is walked instead, because a gate that goes silent
|
|
10
|
+
* outside git is a gate that passes on everything.
|
|
11
|
+
*
|
|
12
|
+
* The sweep REFUSES an answer that is empty while the tree is not. A read that
|
|
13
|
+
* silently sees nothing turns every rule above it vacuously green, which is the
|
|
14
|
+
* one failure mode a gate must never have — and a genuinely bare directory,
|
|
15
|
+
* which the sandboxes of `specs/cli/` build, is told apart from it by looking.
|
|
16
|
+
*
|
|
17
|
+
* It also refuses to answer for the GROUND a spec stands on. `_fixtures/`,
|
|
18
|
+
* `_expected/`, `_stubs/` and `_binary/` hold a tree deliberately broken, a
|
|
19
|
+
* golden held byte for byte, a fake system and a build — each belonging to the
|
|
20
|
+
* scenario that mounts it, not to the project carrying it. A gate judges that
|
|
21
|
+
* tree where the scenario runs it, on its own ground, and never twice.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { execFileSync } from 'node:child_process';
|
|
25
|
+
import { lstatSync, readdirSync, readFileSync } from 'node:fs';
|
|
26
|
+
import { join, matchesGlob } from 'node:path';
|
|
27
|
+
|
|
28
|
+
/** Never source, never prose: what no gate of this toolchain has a question about. */
|
|
29
|
+
const UNSWEPT = new Set([
|
|
30
|
+
'.artifacts',
|
|
31
|
+
'.git',
|
|
32
|
+
'.next',
|
|
33
|
+
'.turbo',
|
|
34
|
+
'.vite',
|
|
35
|
+
'coverage',
|
|
36
|
+
'dist',
|
|
37
|
+
'node_modules',
|
|
38
|
+
]);
|
|
39
|
+
|
|
40
|
+
/** Extensions whose bytes are not text — reading them for patterns is noise. */
|
|
41
|
+
const BINARY = /\.(?:avif|bin|gif|ico|jpeg|jpg|lock|mp4|pdf|png|svg|ttf|webp|woff2?|zip)$/iu;
|
|
42
|
+
|
|
43
|
+
/** Every path git carries or would carry, or null when there is no git tree here. */
|
|
44
|
+
function gitSweep(root) {
|
|
45
|
+
try {
|
|
46
|
+
return execFileSync(
|
|
47
|
+
'git',
|
|
48
|
+
['ls-files', '--cached', '--others', '--exclude-standard', '-z'],
|
|
49
|
+
{ cwd: root, encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] },
|
|
50
|
+
)
|
|
51
|
+
.split('\0')
|
|
52
|
+
.filter((path) => path !== '');
|
|
53
|
+
} catch {
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The same listing, derived from the filesystem, for a tree git does not know. */
|
|
59
|
+
function walkSweep(root) {
|
|
60
|
+
const found = [];
|
|
61
|
+
|
|
62
|
+
function walk(relativePath) {
|
|
63
|
+
let entries;
|
|
64
|
+
try {
|
|
65
|
+
entries = readdirSync(join(root, relativePath), { withFileTypes: true });
|
|
66
|
+
} catch {
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
for (const entry of entries.toSorted((a, b) => (a.name < b.name ? -1 : 1))) {
|
|
71
|
+
if (UNSWEPT.has(entry.name)) {
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
const child = relativePath === '' ? entry.name : `${relativePath}/${entry.name}`;
|
|
75
|
+
if (entry.isDirectory()) {
|
|
76
|
+
walk(child);
|
|
77
|
+
} else if (entry.isFile()) {
|
|
78
|
+
found.push(child);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
walk('');
|
|
84
|
+
|
|
85
|
+
return found;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Whether anything the gates would ever read sits at the root at all. */
|
|
89
|
+
function holdsSomething(root) {
|
|
90
|
+
try {
|
|
91
|
+
return readdirSync(root).some((name) => !UNSWEPT.has(name));
|
|
92
|
+
} catch {
|
|
93
|
+
return false;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* The ground a spec stands on, by the four names `@jterrazz/test` 14 gives it.
|
|
99
|
+
* `_common/` is deliberately absent: it is the project's own shared code, and
|
|
100
|
+
* the marker there says "of the row", not "not mine".
|
|
101
|
+
*/
|
|
102
|
+
const GROUND = new Set(['_binary', '_expected', '_fixtures', '_stubs']);
|
|
103
|
+
|
|
104
|
+
/** Whether a path lies under the ground a scenario mounts rather than in the project. */
|
|
105
|
+
const isGround = (path) => path.split('/').some((segment) => GROUND.has(segment));
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The sweep, filtered by what the run was told to overlook. `ignorePatterns`
|
|
109
|
+
* carries the `--ignore-pattern` globs `check` received, so one flag answers
|
|
110
|
+
* for the linter and for every gate that reads the same tree.
|
|
111
|
+
*/
|
|
112
|
+
export function trackedFiles(root = '.', { ignorePatterns = [] } = {}) {
|
|
113
|
+
const swept = gitSweep(root) ?? walkSweep(root);
|
|
114
|
+
|
|
115
|
+
if (swept.length === 0) {
|
|
116
|
+
if (holdsSomething(root)) {
|
|
117
|
+
throw new Error(
|
|
118
|
+
`nothing to read under ${root} — a gate sweeping no file passes on every file`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return [];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return swept
|
|
126
|
+
.filter((path) => !path.split('/').some((segment) => UNSWEPT.has(segment)))
|
|
127
|
+
.filter((path) => !isGround(path))
|
|
128
|
+
.filter((path) => !ignorePatterns.some((pattern) => matchesGlob(path, pattern)))
|
|
129
|
+
.toSorted((left, right) => (left < right ? -1 : 1));
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* A tracked file's text, or null when there is no text to read: bytes, a file
|
|
134
|
+
* too large to be prose or source, one that is gone, or a SYMLINK — whose
|
|
135
|
+
* content belongs to its target, and is judged there once instead of twice.
|
|
136
|
+
*/
|
|
137
|
+
export function readText(root, path) {
|
|
138
|
+
if (BINARY.test(path)) {
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const stats = lstatSync(join(root, path));
|
|
144
|
+
if (stats.isSymbolicLink() || stats.size > 1_000_000) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
const text = readFileSync(join(root, path), 'utf8');
|
|
148
|
+
|
|
149
|
+
return text.includes('\0') ? null : text;
|
|
150
|
+
} catch {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The `--ignore-pattern` globs of an argument list, in the order they arrived. */
|
|
156
|
+
export function ignorePatternsOf(argv) {
|
|
157
|
+
const patterns = [];
|
|
158
|
+
for (const [index, argument] of argv.entries()) {
|
|
159
|
+
if (argument === '--ignore-pattern' && argv[index + 1] !== undefined) {
|
|
160
|
+
patterns.push(argv[index + 1]);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
return patterns;
|
|
165
|
+
}
|
package/lib/workspace-members.js
CHANGED
|
@@ -29,19 +29,18 @@
|
|
|
29
29
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
30
30
|
import { join, relative, resolve, sep } from 'node:path';
|
|
31
31
|
import { argv, stdout } from 'node:process';
|
|
32
|
-
import { fileURLToPath } from 'node:url';
|
|
33
32
|
|
|
34
33
|
const SKIPPED = new Set(['dist', 'node_modules']);
|
|
35
34
|
|
|
36
35
|
/** A glob over path segments — `*` stops at a separator, `**` does not. */
|
|
37
36
|
function toPattern(glob) {
|
|
38
37
|
const escaped = glob
|
|
39
|
-
.replaceAll(/[.+^${}()|[\]\\]/
|
|
38
|
+
.replaceAll(/[.+^${}()|[\]\\]/gu, String.raw`\$&`)
|
|
40
39
|
.replaceAll('**', ' ')
|
|
41
40
|
.replaceAll('*', '[^/]*')
|
|
42
41
|
.replaceAll(' ', '.*');
|
|
43
42
|
|
|
44
|
-
return new RegExp(`^${escaped}
|
|
43
|
+
return new RegExp(`^${escaped}$`, 'u');
|
|
45
44
|
}
|
|
46
45
|
|
|
47
46
|
/** The `workspaces` globs of a package.json, in either declared form. */
|
|
@@ -70,7 +69,7 @@ export function workspaceMembers(root = '.') {
|
|
|
70
69
|
const absolute = resolve(root);
|
|
71
70
|
const patterns = declaredGlobs(absolute)
|
|
72
71
|
.filter((glob) => typeof glob === 'string' && !glob.startsWith('!'))
|
|
73
|
-
.map((glob) => toPattern(glob.replace(
|
|
72
|
+
.map((glob) => toPattern(glob.replace(/\/+$/u, '')));
|
|
74
73
|
|
|
75
74
|
if (patterns.length === 0) {
|
|
76
75
|
return [];
|
|
@@ -113,11 +112,11 @@ export function workspaceMembers(root = '.') {
|
|
|
113
112
|
|
|
114
113
|
walk(absolute, 0);
|
|
115
114
|
|
|
116
|
-
return [...members].
|
|
115
|
+
return [...members].toSorted((left, right) => (left < right ? -1 : 1));
|
|
117
116
|
}
|
|
118
117
|
|
|
119
118
|
// CLI form — only when this file IS the process entry, never on import.
|
|
120
|
-
if (argv[1] && resolve(argv[1]) ===
|
|
119
|
+
if (argv[1] && resolve(argv[1]) === import.meta.filename) {
|
|
121
120
|
const members = workspaceMembers(argv[2] ?? '.');
|
|
122
121
|
if (members.length > 0) {
|
|
123
122
|
stdout.write(`${members.join('\n')}\n`);
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jterrazz/typescript",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.0.0",
|
|
4
|
+
"license": "MIT",
|
|
4
5
|
"author": "Jean-Baptiste Terrazzoni <contact@jterrazz.com>",
|
|
5
6
|
"repository": {
|
|
6
7
|
"type": "git",
|
|
7
|
-
"url": "https://github.com/jterrazz/package-typescript"
|
|
8
|
+
"url": "git+https://github.com/jterrazz/package-typescript.git"
|
|
8
9
|
},
|
|
9
10
|
"bin": {
|
|
10
11
|
"typescript": "bin/typescript.sh"
|
|
@@ -13,6 +14,7 @@
|
|
|
13
14
|
"bin",
|
|
14
15
|
"lib",
|
|
15
16
|
"presets",
|
|
17
|
+
"rules",
|
|
16
18
|
"src"
|
|
17
19
|
],
|
|
18
20
|
"type": "module",
|
|
@@ -49,16 +51,22 @@
|
|
|
49
51
|
},
|
|
50
52
|
"scripts": {
|
|
51
53
|
"build": "# no build script",
|
|
52
|
-
"lint": "./bin/typescript.sh check
|
|
53
|
-
"lint:fix": "./bin/typescript.sh fix
|
|
54
|
+
"lint": "./bin/typescript.sh check",
|
|
55
|
+
"lint:fix": "./bin/typescript.sh fix",
|
|
54
56
|
"test": "vitest --run"
|
|
55
57
|
},
|
|
56
58
|
"dependencies": {
|
|
59
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
60
|
+
"dependency-cruiser": "^18.3.1",
|
|
57
61
|
"eslint-plugin-perfectionist": "^5.10.0",
|
|
58
|
-
"knip": "^6.
|
|
59
|
-
"oxfmt": "^0.
|
|
60
|
-
"oxlint": "^1.
|
|
61
|
-
"
|
|
62
|
+
"knip": "^6.35.1",
|
|
63
|
+
"oxfmt": "^0.68.0",
|
|
64
|
+
"oxlint": "^1.83.0",
|
|
65
|
+
"oxlint-tsgolint": "7.0.2001",
|
|
66
|
+
"prettier": "^3.9.6",
|
|
67
|
+
"prettier-plugin-astro": "^1.0.0",
|
|
68
|
+
"publint": "^0.3.24",
|
|
69
|
+
"tsdown": "^0.23.0",
|
|
62
70
|
"typedoc": "^0.28.20",
|
|
63
71
|
"typedoc-plugin-markdown": "^4.12.0",
|
|
64
72
|
"typescript": "^6.0.0"
|
|
@@ -76,5 +84,8 @@
|
|
|
76
84
|
"@typescript/typescript-linux-x64": "^7.0.2",
|
|
77
85
|
"@typescript/typescript-win32-arm64": "^7.0.2",
|
|
78
86
|
"@typescript/typescript-win32-x64": "^7.0.2"
|
|
87
|
+
},
|
|
88
|
+
"engines": {
|
|
89
|
+
"node": ">=24.0.0"
|
|
79
90
|
}
|
|
80
91
|
}
|
package/presets/oxfmt/index.js
CHANGED
|
@@ -1,18 +1,62 @@
|
|
|
1
1
|
import { defineConfig } from 'oxfmt';
|
|
2
2
|
|
|
3
|
+
/*
|
|
4
|
+
* The formatting decisions, in one place. Four of them are the estate's and do
|
|
5
|
+
* not move — 100 columns, 4 spaces, single quotes, trailing commas everywhere —
|
|
6
|
+
* because twenty-eight repositories are written that way and two of them hold
|
|
7
|
+
* signed attestation bytes a reflow would invalidate.
|
|
8
|
+
*
|
|
9
|
+
* The three sorters are the fourth law of the rulebook: sorting is formatting.
|
|
10
|
+
* oxfmt owns import order, package.json key order and Tailwind class order, so
|
|
11
|
+
* no lint rule reorders those bytes and no two tools fight over them
|
|
12
|
+
* ([Lint presets](../../docs/07-lint-presets.md)).
|
|
13
|
+
*/
|
|
3
14
|
export default defineConfig({
|
|
15
|
+
bracketSpacing: true,
|
|
16
|
+
endOfLine: 'lf',
|
|
4
17
|
printWidth: 100,
|
|
5
|
-
tabWidth: 4,
|
|
6
|
-
useTabs: false,
|
|
7
18
|
semi: true,
|
|
8
19
|
singleQuote: true,
|
|
20
|
+
/* The grouping the estate has read for three years: builtins and externals
|
|
21
|
+
* together at the top, then internals, then the relative block, then
|
|
22
|
+
* styles. A blank line between groups, case-insensitive inside one. */
|
|
23
|
+
sortImports: {
|
|
24
|
+
groups: [
|
|
25
|
+
['builtin', 'external'],
|
|
26
|
+
['internal', 'subpath'],
|
|
27
|
+
['parent', 'sibling', 'index'],
|
|
28
|
+
'style',
|
|
29
|
+
'unknown',
|
|
30
|
+
],
|
|
31
|
+
ignoreCase: true,
|
|
32
|
+
newlinesBetween: true,
|
|
33
|
+
order: 'asc',
|
|
34
|
+
},
|
|
35
|
+
sortPackageJson: true,
|
|
36
|
+
/* Tailwind's own order, on `class`/`className` and on the class-holding
|
|
37
|
+
* helpers every repo of the estate uses. Regex matchers are not supported
|
|
38
|
+
* yet — these are exact names. */
|
|
39
|
+
sortTailwindcss: {
|
|
40
|
+
functions: ['clsx', 'cn', 'cva', 'tv', 'twMerge', 'twJoin', 'tw'],
|
|
41
|
+
},
|
|
42
|
+
tabWidth: 4,
|
|
9
43
|
trailingComma: 'all',
|
|
10
|
-
|
|
11
|
-
endOfLine: 'lf',
|
|
44
|
+
useTabs: false,
|
|
12
45
|
/* `typescript docs` writes the byte-for-byte typedoc tree to docs/reference/ —
|
|
13
46
|
* formatting it would fight the Docs (sync) pass, so skip it by default.
|
|
14
47
|
* Asset trees and build caches are not source either — and a content tree
|
|
15
48
|
* can hold signed bytes (jterrazz-web's attestations), where one formatter
|
|
16
49
|
* pass invalidates every proof. */
|
|
17
|
-
ignorePatterns: [
|
|
50
|
+
ignorePatterns: [
|
|
51
|
+
'docs/reference',
|
|
52
|
+
'dist/**',
|
|
53
|
+
'assets/**',
|
|
54
|
+
'public/**',
|
|
55
|
+
'.next/**',
|
|
56
|
+
'.expo/**',
|
|
57
|
+
// A spec's input is deliberately mis-shaped and its golden is
|
|
58
|
+
// Byte-for-byte; formatting either would rewrite the claim.
|
|
59
|
+
'**/_fixtures/**',
|
|
60
|
+
'**/_expected/**',
|
|
61
|
+
],
|
|
18
62
|
});
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { defineConfig } from 'oxlint';
|
|
2
|
+
|
|
3
|
+
import { profile } from '../../../rules/compile.js';
|
|
4
|
+
import { PROFILES } from '../../../rules/profiles.js';
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* Astro: the rulebook, plus React (an island is a React component here),
|
|
8
|
+
* accessibility, and the decisions an `.astro` file's own shape forces.
|
|
9
|
+
*/
|
|
10
|
+
export default defineConfig(profile(PROFILES.astro));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { defineConfig } from 'oxlint';
|
|
2
|
+
|
|
3
|
+
import { profile } from '../../../rules/compile.js';
|
|
4
|
+
import { PROFILES } from '../../../rules/profiles.js';
|
|
5
|
+
|
|
6
|
+
/** Bun: the node rulebook, plus the globals Bun's runtime defines. */
|
|
7
|
+
export default defineConfig(profile(PROFILES.bun));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { defineConfig } from 'oxlint';
|
|
2
|
+
|
|
3
|
+
import { profile } from '../../../rules/compile.js';
|
|
4
|
+
import { PROFILES } from '../../../rules/profiles.js';
|
|
5
|
+
|
|
6
|
+
/** Expo and React Native: the rulebook, plus React, accessibility and the platform. */
|
|
7
|
+
export default defineConfig(profile(PROFILES.expo));
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineConfig } from 'oxlint';
|
|
2
|
+
|
|
3
|
+
import { profile } from '../../../rules/compile.js';
|
|
4
|
+
import { PROFILES } from '../../../rules/profiles.js';
|
|
5
|
+
|
|
6
|
+
/*
|
|
7
|
+
* A published package: the node rulebook, plus the one tree a library carries
|
|
8
|
+
* and never lints — the committed typedoc projection.
|
|
9
|
+
*
|
|
10
|
+
* The `.js` extension on every relative import is core's (`import/extensions`
|
|
11
|
+
* at `always`), because Node ESM resolves a specifier literally and a package
|
|
12
|
+
* published as ESM is read by Node before any bundler reads it. It pairs with
|
|
13
|
+
* `presets/tsconfig/library.json`, whose `isolatedDeclarations` is what makes
|
|
14
|
+
* the declarations emit without a type-checker.
|
|
15
|
+
*/
|
|
16
|
+
export default defineConfig(profile(PROFILES.library));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { defineConfig } from 'oxlint';
|
|
2
|
+
|
|
3
|
+
import { profile } from '../../../rules/compile.js';
|
|
4
|
+
import { PROFILES } from '../../../rules/profiles.js';
|
|
5
|
+
|
|
6
|
+
/** Next.js: the rulebook, plus React, accessibility and Next's own 21 rules. */
|
|
7
|
+
export default defineConfig(profile(PROFILES.next));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { defineConfig } from 'oxlint';
|
|
2
|
+
|
|
3
|
+
import { profile } from '../../../rules/compile.js';
|
|
4
|
+
import { PROFILES } from '../../../rules/profiles.js';
|
|
5
|
+
|
|
6
|
+
/** Node.js services and command-line tools: the rulebook, and nothing added. */
|
|
7
|
+
export default defineConfig(profile(PROFILES.node));
|
|
@@ -1,23 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"display": "Expo",
|
|
3
3
|
"include": ["${configDir}/**/*.ts", "${configDir}/**/*.tsx"],
|
|
4
|
-
"exclude": [
|
|
5
|
-
|
|
4
|
+
"exclude": [
|
|
5
|
+
"${configDir}/node_modules",
|
|
6
|
+
"${configDir}/**/_fixtures",
|
|
7
|
+
"${configDir}/**/_expected"
|
|
8
|
+
],
|
|
6
9
|
"compilerOptions": {
|
|
7
10
|
"allowJs": true,
|
|
8
|
-
"baseUrl": ".",
|
|
9
11
|
"esModuleInterop": true,
|
|
12
|
+
"exactOptionalPropertyTypes": true,
|
|
10
13
|
"incremental": true,
|
|
11
14
|
"jsx": "react-native",
|
|
12
15
|
"lib": ["DOM", "ESNext"],
|
|
13
|
-
"
|
|
16
|
+
"module": "ESNext",
|
|
17
|
+
"moduleResolution": "bundler",
|
|
14
18
|
"noEmit": true,
|
|
19
|
+
"noFallthroughCasesInSwitch": true,
|
|
20
|
+
"noImplicitOverride": true,
|
|
21
|
+
"noUncheckedIndexedAccess": true,
|
|
22
|
+
"noUncheckedSideEffectImports": true,
|
|
15
23
|
"paths": {
|
|
16
|
-
"@/*": ["src/*"]
|
|
24
|
+
"@/*": ["${configDir}/src/*"]
|
|
17
25
|
},
|
|
18
26
|
"resolveJsonModule": true,
|
|
19
27
|
"skipLibCheck": true,
|
|
28
|
+
"strict": true,
|
|
20
29
|
"target": "ESNext",
|
|
21
|
-
"tsBuildInfoFile": "${configDir}/.artifacts/tsc/tsconfig.tsbuildinfo"
|
|
30
|
+
"tsBuildInfoFile": "${configDir}/.artifacts/tsc/tsconfig.tsbuildinfo",
|
|
31
|
+
"verbatimModuleSyntax": true
|
|
22
32
|
}
|
|
23
33
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"display": "Library (ESM, declaration-emitting)",
|
|
3
|
+
"extends": "./node.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"allowJs": false,
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"erasableSyntaxOnly": true,
|
|
8
|
+
"isolatedDeclarations": true
|
|
9
|
+
},
|
|
10
|
+
"include": ["${configDir}/**/*.ts"],
|
|
11
|
+
"exclude": [
|
|
12
|
+
"${configDir}/node_modules",
|
|
13
|
+
"${configDir}/dist",
|
|
14
|
+
"${configDir}/.artifacts",
|
|
15
|
+
"${configDir}/**/_fixtures",
|
|
16
|
+
"${configDir}/**/_expected"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -18,7 +18,13 @@
|
|
|
18
18
|
{
|
|
19
19
|
"name": "next"
|
|
20
20
|
}
|
|
21
|
-
]
|
|
21
|
+
],
|
|
22
|
+
"exactOptionalPropertyTypes": true,
|
|
23
|
+
"noFallthroughCasesInSwitch": true,
|
|
24
|
+
"noImplicitOverride": true,
|
|
25
|
+
"noUncheckedIndexedAccess": true,
|
|
26
|
+
"noUncheckedSideEffectImports": true,
|
|
27
|
+
"verbatimModuleSyntax": true
|
|
22
28
|
},
|
|
23
29
|
"include": [
|
|
24
30
|
"${configDir}/next-env.d.ts",
|
|
@@ -27,5 +33,9 @@
|
|
|
27
33
|
"${configDir}/**/*.ts",
|
|
28
34
|
"${configDir}/**/*.tsx"
|
|
29
35
|
],
|
|
30
|
-
"exclude": [
|
|
36
|
+
"exclude": [
|
|
37
|
+
"${configDir}/node_modules",
|
|
38
|
+
"${configDir}/**/_fixtures",
|
|
39
|
+
"${configDir}/**/_expected"
|
|
40
|
+
]
|
|
31
41
|
}
|
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"display": "Node ESM",
|
|
3
|
-
"include": ["${configDir}/**/*.ts"],
|
|
4
|
-
"exclude": [
|
|
5
|
-
|
|
3
|
+
"include": ["${configDir}/**/*.ts", "${configDir}/**/*.js"],
|
|
4
|
+
"exclude": [
|
|
5
|
+
"${configDir}/node_modules",
|
|
6
|
+
"${configDir}/dist",
|
|
7
|
+
"${configDir}/.artifacts",
|
|
8
|
+
"${configDir}/**/_fixtures",
|
|
9
|
+
"${configDir}/**/_expected"
|
|
10
|
+
],
|
|
6
11
|
"compilerOptions": {
|
|
7
12
|
"moduleResolution": "Bundler",
|
|
8
13
|
"module": "ESNext",
|
|
9
14
|
"target": "ESNext",
|
|
10
15
|
"types": ["node"],
|
|
11
16
|
"strict": true,
|
|
17
|
+
"exactOptionalPropertyTypes": true,
|
|
18
|
+
"noFallthroughCasesInSwitch": true,
|
|
19
|
+
"noImplicitOverride": true,
|
|
20
|
+
"noUncheckedIndexedAccess": true,
|
|
21
|
+
"noUncheckedSideEffectImports": true,
|
|
22
|
+
"verbatimModuleSyntax": true,
|
|
12
23
|
"incremental": true,
|
|
13
24
|
"tsBuildInfoFile": "${configDir}/.artifacts/tsc/tsconfig.tsbuildinfo",
|
|
14
25
|
"experimentalDecorators": true,
|
|
15
26
|
"esModuleInterop": true,
|
|
16
27
|
"resolveJsonModule": true,
|
|
17
|
-
"skipLibCheck": true
|
|
28
|
+
"skipLibCheck": true,
|
|
29
|
+
"allowJs": true,
|
|
30
|
+
"checkJs": false,
|
|
31
|
+
"noEmit": true
|
|
18
32
|
}
|
|
19
33
|
}
|
package/rules/README.md
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# `rules/` — the manifest
|
|
2
|
+
|
|
3
|
+
One nature lives here: a DECISION about a lint rule. Never a config object, never a path, never a tool invocation — those are `presets/`'s and `bin/`'s. What the decisions mean, and the four laws they answer to, is [Lint presets](../docs/07-lint-presets.md); this page only says where each thing is.
|
|
4
|
+
|
|
5
|
+
| File | Holds |
|
|
6
|
+
| ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
7
|
+
| `_contract.js` | `fragment()`, `on()`, `typeAware()`, `off()`, `scoped()` — and the load-time refusal of a level that is not `error`/`off` and of an `off` with no reason |
|
|
8
|
+
| `compile.js` | fragment → plain oxlint config, and the deterministic merge behind `compose()`. It never emits `categories` |
|
|
9
|
+
| `profiles.js` | which fragments each of the six profiles carries, and what none of them lints |
|
|
10
|
+
| `catalog.js` | every decision as one list, and the markdown the chapter carries between its `GENERATED` markers |
|
|
11
|
+
| `core/` | one file per plugin of the rulebook every profile holds: `eslint`, `typescript`, `unicorn`, `oxc`, `import`, `promise`, `node`, `jsdoc` |
|
|
12
|
+
| `react.js` · `a11y.js` · `next.js` · `react-native.js` · `astro.js` | what a framework profile adds to that rulebook |
|
|
13
|
+
| `vitest.js` | the test-file rules, as an `overrides` block — they read a test and say nothing about anything else |
|
|
14
|
+
| `sorted.js` | perfectionist: only what oxfmt does not sort |
|
|
15
|
+
| `architecture/` | `layers.js` turns a declared layer map into `no-restricted-imports` overrides; `hexagonal.js` is the map this package ships |
|
|
16
|
+
|
|
17
|
+
A fragment is loaded, not read: importing one runs its contract, so a decision that breaks an invariant fails at import time rather than at review time.
|
|
18
|
+
|
|
19
|
+
## What proves it
|
|
20
|
+
|
|
21
|
+
- `_contract.test.ts` — the invariants, and the compile of a fragment.
|
|
22
|
+
- `catalog.test.ts` — the chapter is the manifest's projection, and nothing else.
|
|
23
|
+
- `specs/cli/preset/` — the resolved rule set, the plugin coverage, the fixpoint against the formatter, the behaviour of each decision, and the exclusive pairs.
|