@jterrazz/typescript 9.0.1 → 9.1.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/lib/merge-knip-config.js +94 -2
- package/package.json +5 -1
- package/src/oxfmt.d.ts +6 -0
- package/src/oxfmt.js +14 -0
- package/src/oxfmt.test.ts +17 -0
- package/src/oxlint.d.ts +1 -0
- package/src/oxlint.js +10 -2
- package/src/oxlint.test.ts +9 -1
package/lib/merge-knip-config.js
CHANGED
|
@@ -6,6 +6,11 @@
|
|
|
6
6
|
*
|
|
7
7
|
* Usage: node merge-knip-config.js <base.json> [project-knip.json]
|
|
8
8
|
*
|
|
9
|
+
* The project file is read as JSONC — comments and trailing commas — so an
|
|
10
|
+
* ignore can carry the reason it exists. Every entry knip is told to overlook
|
|
11
|
+
* is a judgement, and a judgement with no reason beside it is re-litigated by
|
|
12
|
+
* the next reader.
|
|
13
|
+
*
|
|
9
14
|
* Merge rules:
|
|
10
15
|
* - Arrays (ignoreDependencies, ignoreBinaries, ignore): concatenated and deduplicated
|
|
11
16
|
* - Objects (rules, vitest, etc.): project values override base
|
|
@@ -29,14 +34,101 @@ import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
|
29
34
|
|
|
30
35
|
import { workspaceMembers } from './workspace-members.js';
|
|
31
36
|
|
|
37
|
+
/**
|
|
38
|
+
* Drops `//` and block comments, leaving everything inside a string literal
|
|
39
|
+
* alone — a comment marker in a glob (`ignore: ["**\/*.js"]`) is data.
|
|
40
|
+
*/
|
|
41
|
+
function withoutComments(text) {
|
|
42
|
+
let output = '',
|
|
43
|
+
index = 0,
|
|
44
|
+
inString = false;
|
|
45
|
+
|
|
46
|
+
while (index < text.length) {
|
|
47
|
+
const char = text[index];
|
|
48
|
+
|
|
49
|
+
if (inString) {
|
|
50
|
+
const escaped = char === '\\';
|
|
51
|
+
output += escaped ? char + (text[index + 1] ?? '') : char;
|
|
52
|
+
inString = escaped || char !== '"';
|
|
53
|
+
index += escaped ? 2 : 1;
|
|
54
|
+
} else if (char === '"') {
|
|
55
|
+
inString = true;
|
|
56
|
+
output += char;
|
|
57
|
+
index += 1;
|
|
58
|
+
} else if (char === '/' && text[index + 1] === '/') {
|
|
59
|
+
while (index < text.length && text[index] !== '\n') {
|
|
60
|
+
index += 1;
|
|
61
|
+
}
|
|
62
|
+
} else if (char === '/' && text[index + 1] === '*') {
|
|
63
|
+
const end = text.indexOf('*/', index + 2);
|
|
64
|
+
index = end === -1 ? text.length : end + 2;
|
|
65
|
+
} else {
|
|
66
|
+
output += char;
|
|
67
|
+
index += 1;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return output;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Whether the comma at `index` is the last one of its collection — `["a",]`. */
|
|
75
|
+
function closesCollection(text, index) {
|
|
76
|
+
if (text[index] !== ',') {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let next = index + 1;
|
|
81
|
+
while (next < text.length && text[next].trim() === '') {
|
|
82
|
+
next += 1;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return text[next] === ']' || text[next] === '}';
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Drops a comma that closes its collection, outside string literals. */
|
|
89
|
+
function withoutTrailingCommas(text) {
|
|
90
|
+
let output = '',
|
|
91
|
+
index = 0,
|
|
92
|
+
inString = false;
|
|
93
|
+
|
|
94
|
+
while (index < text.length) {
|
|
95
|
+
const char = text[index];
|
|
96
|
+
const closes = closesCollection(text, index);
|
|
97
|
+
|
|
98
|
+
if (inString) {
|
|
99
|
+
const escaped = char === '\\';
|
|
100
|
+
output += escaped ? char + (text[index + 1] ?? '') : char;
|
|
101
|
+
inString = escaped || char !== '"';
|
|
102
|
+
index += escaped ? 2 : 1;
|
|
103
|
+
} else {
|
|
104
|
+
inString = char === '"';
|
|
105
|
+
output += closes ? '' : char;
|
|
106
|
+
index += 1;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return output;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Reads a JSONC document. Hand-written on purpose: no JSONC parser is in this
|
|
115
|
+
* package's tree, and two token classes do not earn a dependency.
|
|
116
|
+
*/
|
|
117
|
+
function readJsonc(path) {
|
|
118
|
+
const document = readFileSync(path, 'utf8'),
|
|
119
|
+
json = withoutTrailingCommas(withoutComments(document));
|
|
120
|
+
|
|
121
|
+
return JSON.parse(json);
|
|
122
|
+
}
|
|
123
|
+
|
|
32
124
|
const basePath = process.argv[2];
|
|
33
125
|
const projectPath = process.argv[3];
|
|
34
126
|
|
|
35
|
-
const base =
|
|
127
|
+
const base = readJsonc(basePath);
|
|
36
128
|
|
|
37
129
|
let project = {};
|
|
38
130
|
if (projectPath) {
|
|
39
|
-
project =
|
|
131
|
+
project = readJsonc(projectPath);
|
|
40
132
|
}
|
|
41
133
|
|
|
42
134
|
const ARRAY_KEYS = new Set(['ignore', 'ignoreBinaries', 'ignoreDependencies', 'entry', 'project']);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jterrazz/typescript",
|
|
3
|
-
"version": "9.0
|
|
3
|
+
"version": "9.1.0",
|
|
4
4
|
"author": "Jean-Baptiste Terrazzoni <contact@jterrazz.com>",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -23,6 +23,10 @@
|
|
|
23
23
|
"types": "./src/index.d.ts",
|
|
24
24
|
"default": "./src/index.js"
|
|
25
25
|
},
|
|
26
|
+
"./oxfmt": {
|
|
27
|
+
"types": "./src/oxfmt.d.ts",
|
|
28
|
+
"default": "./src/oxfmt.js"
|
|
29
|
+
},
|
|
26
30
|
"./oxlint": {
|
|
27
31
|
"types": "./src/oxlint.d.ts",
|
|
28
32
|
"default": "./src/oxlint.js"
|
package/src/oxfmt.d.ts
ADDED
package/src/oxfmt.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* The tool-facing oxfmt entry (`@jterrazz/typescript/oxfmt`): the shared
|
|
3
|
+
* formatting preset plus oxfmt's own `defineConfig`, for the same reason
|
|
4
|
+
* `oxlint.js` re-exports its tool's — a consumer's config names this package
|
|
5
|
+
* and nothing else:
|
|
6
|
+
*
|
|
7
|
+
* import { base, defineConfig } from '@jterrazz/typescript/oxfmt';
|
|
8
|
+
*
|
|
9
|
+
* export default defineConfig(base);
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export { defineConfig } from 'oxfmt';
|
|
13
|
+
|
|
14
|
+
export { default as base } from '../presets/oxfmt/index.js';
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { expect, test } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { base, defineConfig } from './oxfmt.js';
|
|
4
|
+
|
|
5
|
+
test('exports the shared formatting preset', () => {
|
|
6
|
+
// Given - the tool-facing entry
|
|
7
|
+
// Then - the preset carries the ecosystem's format: four spaces, single quotes, 100 columns
|
|
8
|
+
expect(base).toMatchObject({ printWidth: 100, singleQuote: true, tabWidth: 4 });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test("re-exports oxfmt's own defineConfig", () => {
|
|
12
|
+
// Given - a config a consumer would write, with oxfmt declared nowhere in its project
|
|
13
|
+
const config = { printWidth: 80 };
|
|
14
|
+
|
|
15
|
+
// Then - the entry carries the tool's helper, which returns the config unchanged
|
|
16
|
+
expect(defineConfig(config)).toBe(config);
|
|
17
|
+
});
|
package/src/oxlint.d.ts
CHANGED
package/src/oxlint.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/*
|
|
2
2
|
* The tool-facing oxlint entry (`@jterrazz/typescript/oxlint`): the named
|
|
3
|
-
* presets
|
|
4
|
-
* composes exactly the fragments it wants, nothing is
|
|
3
|
+
* presets, the `compose()` helper, and oxlint's own `defineConfig`. Wiring is
|
|
4
|
+
* EXPLICIT — a consumer composes exactly the fragments it wants, nothing is
|
|
5
|
+
* auto-detected:
|
|
5
6
|
*
|
|
6
7
|
* import { testing } from '@jterrazz/test/oxlint';
|
|
7
8
|
* import { compose, node } from '@jterrazz/typescript/oxlint';
|
|
@@ -50,6 +51,13 @@ export function compose(...fragments) {
|
|
|
50
51
|
return merged;
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
/* Oxlint's own `defineConfig`, re-exported from here: a bare
|
|
55
|
+
* `import { defineConfig } from 'oxlint'` in a consumer's config resolves only
|
|
56
|
+
* where the consumer declares oxlint itself, which pnpm's strict node_modules
|
|
57
|
+
* refuses to assume. Resolved from THIS package — where oxlint is a real
|
|
58
|
+
* dependency — the one-devDependency shape holds on every package manager. */
|
|
59
|
+
export { defineConfig } from 'oxlint';
|
|
60
|
+
|
|
53
61
|
export { default as hexagonal } from '../presets/oxlint/architectures/hexagonal.js';
|
|
54
62
|
export { default as expo } from '../presets/oxlint/expo.js';
|
|
55
63
|
export { default as next } from '../presets/oxlint/next.js';
|
package/src/oxlint.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { expect, test } from 'vitest';
|
|
2
2
|
|
|
3
|
-
import { compose, expo, hexagonal, next, node } from './oxlint.js';
|
|
3
|
+
import { compose, defineConfig, expo, hexagonal, next, node } from './oxlint.js';
|
|
4
4
|
|
|
5
5
|
test('concatenates and dedupes plugin lists', () => {
|
|
6
6
|
// Given - two fragments sharing one jsPlugin
|
|
@@ -77,3 +77,11 @@ test('exports the named presets', () => {
|
|
|
77
77
|
expect(typeof preset).toBe('object');
|
|
78
78
|
}
|
|
79
79
|
});
|
|
80
|
+
|
|
81
|
+
test("re-exports oxlint's own defineConfig", () => {
|
|
82
|
+
// Given - a config a consumer would write, with oxlint declared nowhere in its project
|
|
83
|
+
const config = { rules: { curly: 'error' } };
|
|
84
|
+
|
|
85
|
+
// Then - the entry carries the tool's helper, which returns the config unchanged
|
|
86
|
+
expect(defineConfig(config)).toBe(config);
|
|
87
|
+
});
|