@storm-software/eslint-plugin-bun 0.0.6 → 0.0.7

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.
Files changed (34) hide show
  1. package/README.md +1 -1
  2. package/dist/configs/index.d.mts +5 -0
  3. package/dist/configs/index.mjs +4 -943
  4. package/dist/configs/recommended.d.mts +5 -0
  5. package/dist/configs/recommended.mjs +33 -939
  6. package/dist/helpers/bun-workspace.d.mts +49 -0
  7. package/dist/helpers/bun-workspace.mjs +197 -244
  8. package/dist/helpers/{create-rule.d.ts → create-rule.d.mts} +6 -6
  9. package/dist/helpers/create-rule.mjs +37 -38
  10. package/dist/helpers/{iterate-dependencies.d.ts → iterate-dependencies.d.mts} +7 -7
  11. package/dist/helpers/iterate-dependencies.mjs +36 -56
  12. package/dist/index.d.mts +52 -0
  13. package/dist/index.mjs +29 -951
  14. package/dist/plugin-9WjNsG8_.mjs +20 -0
  15. package/dist/plugin.d.mts +31 -0
  16. package/dist/plugin.mjs +1 -807
  17. package/dist/rules/json-enforce-catalog.d.mts +14 -0
  18. package/dist/rules/json-enforce-catalog.mjs +102 -444
  19. package/dist/rules/json-no-duplicate-catalog-item.d.mts +8 -0
  20. package/dist/rules/json-no-duplicate-catalog-item.mjs +72 -386
  21. package/dist/rules/json-no-unused-catalog-item.d.mts +5 -0
  22. package/dist/rules/json-no-unused-catalog-item.mjs +70 -423
  23. package/dist/rules/json-valid-catalog.d.mts +11 -0
  24. package/dist/rules/json-valid-catalog.mjs +80 -422
  25. package/package.json +15 -22
  26. package/dist/configs/index.d.ts +0 -5
  27. package/dist/configs/recommended.d.ts +0 -5
  28. package/dist/helpers/bun-workspace.d.ts +0 -48
  29. package/dist/index.d.ts +0 -58
  30. package/dist/plugin.d.ts +0 -33
  31. package/dist/rules/json-enforce-catalog.d.ts +0 -18
  32. package/dist/rules/json-no-duplicate-catalog-item.d.ts +0 -12
  33. package/dist/rules/json-no-unused-catalog-item.d.ts +0 -7
  34. package/dist/rules/json-valid-catalog.d.ts +0 -15
@@ -1,429 +1,76 @@
1
- import fs from 'node:fs';
2
- import defu from 'defu';
3
- import process from 'node:process';
4
- import { findUpSync } from 'find-up-simple';
5
- import { normalize, resolve, dirname } from 'pathe';
6
- import { globSync } from 'tinyglobby';
7
-
8
- // src/rules/json-no-unused-catalog-item.ts
9
- function RuleCreator(urlCreator) {
10
- return function createNamedRule({
11
- name,
12
- meta,
13
- ...rule
14
- }) {
15
- return createRuleInfo({
16
- meta: {
17
- ...meta,
18
- docs: {
19
- ...meta.docs,
20
- url: urlCreator(name)
21
- }
22
- },
23
- ...rule
24
- });
25
- };
26
- }
27
- function createRuleInfo({
28
- create,
29
- defaultOptions,
30
- meta
31
- }) {
32
- return {
33
- create: ((context) => {
34
- const optionsWithDefault = context.options.map((options, index) => {
35
- return defu(defaultOptions?.[index] ?? {}, options);
36
- });
37
- return create(context, optionsWithDefault);
38
- }),
39
- defaultOptions: defaultOptions ?? {},
40
- meta
41
- };
42
- }
43
- var createRule = RuleCreator(
44
- (ruleName) => `https://docs.stormsoftware.com/projects/storm-ops/eslint-plugin-bun/rules/${ruleName}.md`
45
- );
46
- function isWorkspaceObject(workspaces) {
47
- return Boolean(workspaces) && typeof workspaces === "object" && !Array.isArray(workspaces);
48
- }
49
- function detectCatalogRoot(data) {
50
- if (data.catalog || data.catalogs) {
51
- return "top";
52
- }
53
- if (isWorkspaceObject(data.workspaces) && (data.workspaces.catalog || data.workspaces.catalogs)) {
54
- return "workspaces";
55
- }
56
- return isWorkspaceObject(data.workspaces) ? "workspaces" : "top";
57
- }
58
- function normalizeCatalogName(name) {
59
- return name.replace(/\^/g, "h").replace(/~/g, "t").replace(/\./g, "_").replace(/</g, "l").replace(/>/g, "g").replace(/=/g, "e").replace(/\*/g, "s").replace(/@/g, "a").replace(/\|/g, "p").replace(/&/g, "n").replace(/-/g, "m").replace(/\+/g, "u").replace(/\s/g, "_");
60
- }
61
- function sortObjectKeys(input) {
62
- return Object.fromEntries(
63
- Object.entries(input).sort(([left], [right]) => left.localeCompare(right))
64
- );
65
- }
66
- function isWorkspaceRootPackageJson(data) {
67
- return Boolean(
68
- data.workspaces || data.catalog || data.catalogs
69
- );
70
- }
71
- function readBunWorkspace(cwd = process.cwd()) {
72
- const filepath = findUpSync("package.json", {
73
- cwd,
74
- type: "file"
75
- });
76
- if (!filepath) {
77
- throw new Error("package.json not found");
78
- }
79
- let current = filepath;
80
- let content = fs.readFileSync(current, "utf8");
81
- let data = JSON.parse(content);
82
- while (!isWorkspaceRootPackageJson(data)) {
83
- const searchFrom = dirname(dirname(current));
84
- const next = findUpSync("package.json", {
85
- cwd: searchFrom,
86
- type: "file"
87
- });
88
- if (!next || normalize(next) === normalize(current)) {
89
- break;
90
- }
91
- current = next;
92
- content = fs.readFileSync(current, "utf8");
93
- data = JSON.parse(content);
94
- }
95
- if (!isWorkspaceRootPackageJson(data)) {
96
- throw new Error(
97
- "Bun workspace root package.json with workspaces/catalog not found"
98
- );
99
- }
100
- let hasChanged = false;
101
- let queueTimer;
102
- const queue = [];
103
- let catalogRoot = detectCatalogRoot(data);
104
- const write = () => {
105
- if (!fs.existsSync(current)) {
106
- return;
107
- }
108
- fs.writeFileSync(current, `${JSON.stringify(data, null, 2)}
109
- `);
110
- };
111
- const clearQueue = () => {
112
- if (queueTimer != null) {
113
- clearTimeout(queueTimer);
114
- queueTimer = void 0;
115
- }
116
- queue.length = 0;
117
- };
118
- const getCatalogContainer = () => {
119
- if (catalogRoot === "workspaces") {
120
- if (!isWorkspaceObject(data.workspaces)) {
121
- data.workspaces = {
122
- packages: Array.isArray(data.workspaces) ? data.workspaces : [],
123
- catalog: {},
124
- catalogs: {}
125
- };
126
- }
127
- return data.workspaces;
128
- }
129
- return data;
130
- };
131
- const getCatalogData = () => {
132
- const container = getCatalogContainer();
133
- return {
134
- catalog: container.catalog,
135
- catalogs: container.catalogs
136
- };
137
- };
138
- const setPackage = (catalogName, packageName, specifier) => {
139
- const container = getCatalogContainer();
140
- if (catalogName === "default") {
141
- container.catalog ??= {};
142
- if (container.catalog[packageName] !== specifier) {
143
- container.catalog[packageName] = specifier;
144
- container.catalog = sortObjectKeys(container.catalog);
145
- hasChanged = true;
146
- }
147
- return;
148
- }
149
- container.catalogs ??= {};
150
- container.catalogs[catalogName] ??= {};
151
- if (container.catalogs[catalogName][packageName] !== specifier) {
152
- container.catalogs[catalogName][packageName] = specifier;
153
- container.catalogs[catalogName] = sortObjectKeys(
154
- container.catalogs[catalogName]
155
- );
156
- container.catalogs = sortObjectKeys(container.catalogs);
157
- hasChanged = true;
158
- }
159
- };
160
- const hasSpecifierConflicts = (catalogName, packageName, specifier) => {
161
- const { catalog, catalogs } = getCatalogData();
162
- const existingSpecifier = catalogName === "default" ? catalog?.[packageName] : catalogs?.[catalogName]?.[packageName];
163
- if (existingSpecifier === specifier) {
164
- return {
165
- conflicts: false,
166
- newCatalogName: catalogName,
167
- existingSpecifier
168
- };
169
- }
170
- if (existingSpecifier) {
171
- const versionSuffix = normalizeCatalogName(specifier);
172
- const newCatalogName = catalogName === "default" ? `conflicts_${packageName}_${versionSuffix}` : `conflicts_${catalogName}_${versionSuffix}`;
173
- return {
174
- conflicts: true,
175
- existingSpecifier,
176
- newCatalogName
177
- };
178
- }
179
- return {
180
- conflicts: false,
181
- newCatalogName: catalogName
182
- };
183
- };
184
- const getPackageCatalogs = (packageName) => {
185
- const found = [];
186
- const { catalog, catalogs } = getCatalogData();
187
- if (catalogs) {
188
- for (const name of Object.keys(catalogs)) {
189
- if (catalogs[name]?.[packageName]) {
190
- found.push(name);
191
- }
192
- }
193
- }
194
- if (catalog?.[packageName]) {
195
- found.push("default");
196
- }
197
- return found;
198
- };
199
- const workspace2 = {
200
- filepath: current,
201
- setContent(newContent) {
202
- content = newContent;
203
- data = JSON.parse(newContent);
204
- catalogRoot = detectCatalogRoot(data);
205
- hasChanged = false;
206
- },
207
- hasChanged: () => hasChanged,
208
- hasQueue: () => queueTimer != null,
209
- clearQueue,
210
- queueChange(fn, order) {
211
- if (order === "pre") {
212
- queue.unshift(fn);
213
- } else {
214
- queue.push(fn);
215
- }
216
- if (queueTimer != null) {
217
- clearTimeout(queueTimer);
218
- }
219
- queueTimer = setTimeout(() => {
220
- queueTimer = void 0;
221
- const clone = [...queue];
222
- queue.length = 0;
223
- for (const queued of clone) {
224
- queued(workspace2);
225
- }
226
- if (hasChanged) {
227
- write();
228
- }
229
- }, 1e3);
230
- },
231
- toJSON: () => data,
232
- toString: () => `${JSON.stringify(data, null, 2)}
233
- `,
234
- setPackage,
235
- setPackageNoConflicts(catalogName, packageName, specifier) {
236
- const { newCatalogName } = hasSpecifierConflicts(
237
- catalogName,
238
- packageName,
239
- specifier
240
- );
241
- setPackage(newCatalogName, packageName, specifier);
242
- },
243
- getPackageCatalogs,
244
- hasSpecifierConflicts,
245
- getCatalogData,
246
- getWorkspacePackagePatterns() {
247
- if (Array.isArray(data.workspaces)) {
248
- return data.workspaces;
249
- }
250
- if (isWorkspaceObject(data.workspaces) && data.workspaces.packages) {
251
- return data.workspaces.packages;
252
- }
253
- return [];
254
- }
255
- };
256
- return workspace2;
257
- }
258
- var WORKSPACE_CACHE_TIME = 1e4;
259
- var workspaceLastRead;
260
- var workspace;
261
- function getBunWorkspace(cwd = process.cwd()) {
262
- if (workspaceLastRead && workspace && !workspace.hasQueue() && Date.now() - workspaceLastRead > WORKSPACE_CACHE_TIME) {
263
- workspace = void 0;
264
- }
265
- if (!workspace) {
266
- workspace = readBunWorkspace(cwd);
267
- workspaceLastRead = Date.now();
268
- }
269
- return workspace;
270
- }
271
- function isRootWorkspacePackageJson(filename, cwd = process.cwd()) {
272
- try {
273
- const root = getBunWorkspace(cwd);
274
- return normalize(root.filepath) === normalize(filename);
275
- } catch {
276
- return false;
277
- }
278
- }
279
- function collectWorkspacePackageJsonPaths(workspaceRoot) {
280
- const root = resolve(dirname(workspaceRoot.filepath));
281
- const patterns = workspaceRoot.getWorkspacePackagePatterns();
282
- const dirs = patterns.length ? globSync(patterns, {
283
- cwd: root,
284
- dot: false,
285
- ignore: [
286
- "**/node_modules/**",
287
- "**/dist/**",
288
- "**/build/**"
289
- ],
290
- absolute: true,
291
- expandDirectories: false,
292
- onlyDirectories: true
293
- }) : [];
294
- dirs.push(root);
295
- return dirs.map((dir) => resolve(dir, "package.json")).filter((path) => fs.existsSync(path)).sort();
296
- }
297
-
298
- // src/helpers/iterate-dependencies.ts
299
- function getPackageJsonRootNode(context) {
300
- if (!context.filename.endsWith("package.json")) {
301
- return void 0;
302
- }
303
- const ast = context.sourceCode.ast;
304
- const root = ast.body[0];
305
- if (root?.expression?.type === "JSONObjectExpression") {
306
- return root.expression;
307
- }
308
- return void 0;
309
- }
310
- function getObjectPath(obj, path) {
311
- let current = obj;
312
- for (const key of path) {
313
- if (!current || typeof current !== "object") {
314
- return void 0;
315
- }
316
- current = current[key];
317
- if (!current) {
318
- return void 0;
319
- }
320
- }
321
- return current;
322
- }
323
-
324
- // src/rules/json-no-unused-catalog-item.ts
325
- var RULE_NAME = "json-no-unused-catalog-item";
326
- var FIELDS = [
327
- "dependencies",
328
- "devDependencies",
329
- "peerDependencies",
330
- "optionalDependencies",
331
- "overrides",
332
- "resolutions"
1
+ import { createRule } from "../helpers/create-rule.mjs";
2
+ import { collectWorkspacePackageJsonPaths, getBunWorkspace, isRootWorkspacePackageJson } from "../helpers/bun-workspace.mjs";
3
+ import { getObjectPath, getPackageJsonRootNode } from "../helpers/iterate-dependencies.mjs";
4
+ import fs from "node:fs";
5
+ //#region src/rules/json-no-unused-catalog-item.ts
6
+ const RULE_NAME = "json-no-unused-catalog-item";
7
+ const FIELDS = [
8
+ "dependencies",
9
+ "devDependencies",
10
+ "peerDependencies",
11
+ "optionalDependencies",
12
+ "overrides",
13
+ "resolutions"
333
14
  ];
334
15
  function getProperty(node, key) {
335
- return node?.properties?.find(
336
- (property) => property.key.type === "JSONLiteral" && property.key.value === key
337
- );
16
+ return node?.properties?.find((property) => property.key.type === "JSONLiteral" && property.key.value === key);
338
17
  }
339
18
  var json_no_unused_catalog_item_default = createRule({
340
- name: RULE_NAME,
341
- meta: {
342
- type: "problem",
343
- docs: {
344
- description: "Disallow unused catalog items in the Bun workspace root `package.json`"
345
- },
346
- schema: [],
347
- messages: {
348
- unusedCatalogItem: 'Catalog item "{{catalogItem}}" is not used in any package.json.'
349
- }
350
- },
351
- defaultOptions: [],
352
- create(context) {
353
- if (!isRootWorkspacePackageJson(context.filename)) {
354
- return {};
355
- }
356
- let workspace2;
357
- try {
358
- workspace2 = getBunWorkspace();
359
- } catch {
360
- return {};
361
- }
362
- if (workspace2.hasChanged() || workspace2.hasQueue()) {
363
- return {};
364
- }
365
- workspace2.setContent(context.sourceCode.text);
366
- const root = getPackageJsonRootNode(context);
367
- if (!root) {
368
- return {};
369
- }
370
- const topCatalog = getProperty(root, "catalog");
371
- const topCatalogs = getProperty(root, "catalogs");
372
- const workspaces = getProperty(root, "workspaces");
373
- const workspacesCatalog = workspaces?.value?.type === "JSONObjectExpression" ? getProperty(workspaces.value, "catalog") : void 0;
374
- const workspacesCatalogs = workspaces?.value?.type === "JSONObjectExpression" ? getProperty(workspaces.value, "catalogs") : void 0;
375
- const catalogNode = topCatalog ?? workspacesCatalog;
376
- const catalogsNode = topCatalogs ?? workspacesCatalogs;
377
- const entries = /* @__PURE__ */ new Map();
378
- if (catalogNode?.value?.type === "JSONObjectExpression") {
379
- for (const property of catalogNode.value.properties || []) {
380
- entries.set(`${String(property.key.value)}:default`, property);
381
- }
382
- }
383
- if (catalogsNode?.value?.type === "JSONObjectExpression") {
384
- for (const named of catalogsNode.value.properties || []) {
385
- if (named.value.type !== "JSONObjectExpression") {
386
- continue;
387
- }
388
- const catalogName = String(named.key.value);
389
- for (const property of named.value.properties || []) {
390
- entries.set(`${String(property.key.value)}:${catalogName}`, property);
391
- }
392
- }
393
- }
394
- if (entries.size === 0) {
395
- return {};
396
- }
397
- const packages = collectWorkspacePackageJsonPaths(workspace2);
398
- for (const path of packages) {
399
- const pkg = JSON.parse(fs.readFileSync(path, "utf8"));
400
- for (const field of FIELDS) {
401
- const map = getObjectPath(pkg, field.split("."));
402
- if (!map) {
403
- continue;
404
- }
405
- for (const [name, value] of Object.entries(map)) {
406
- if (typeof value !== "string" || !value.startsWith("catalog:")) {
407
- continue;
408
- }
409
- const catalogName = value.slice(8) || "default";
410
- entries.delete(`${name}:${catalogName}`);
411
- }
412
- }
413
- }
414
- if (entries.size > 0) {
415
- for (const [key, property] of Array.from(entries.entries()).sort(
416
- (a, b) => a[0].localeCompare(b[0])
417
- )) {
418
- context.report({
419
- node: property.value,
420
- messageId: "unusedCatalogItem",
421
- data: { catalogItem: key }
422
- });
423
- }
424
- }
425
- return {};
426
- }
19
+ name: RULE_NAME,
20
+ meta: {
21
+ type: "problem",
22
+ docs: { description: "Disallow unused catalog items in the Bun workspace root `package.json`" },
23
+ schema: [],
24
+ messages: { unusedCatalogItem: "Catalog item \"{{catalogItem}}\" is not used in any package.json." }
25
+ },
26
+ defaultOptions: [],
27
+ create(context) {
28
+ if (!isRootWorkspacePackageJson(context.filename)) return {};
29
+ let workspace;
30
+ try {
31
+ workspace = getBunWorkspace();
32
+ } catch {
33
+ return {};
34
+ }
35
+ if (workspace.hasChanged() || workspace.hasQueue()) return {};
36
+ workspace.setContent(context.sourceCode.text);
37
+ const root = getPackageJsonRootNode(context);
38
+ if (!root) return {};
39
+ const topCatalog = getProperty(root, "catalog");
40
+ const topCatalogs = getProperty(root, "catalogs");
41
+ const workspaces = getProperty(root, "workspaces");
42
+ const workspacesCatalog = workspaces?.value?.type === "JSONObjectExpression" ? getProperty(workspaces.value, "catalog") : void 0;
43
+ const workspacesCatalogs = workspaces?.value?.type === "JSONObjectExpression" ? getProperty(workspaces.value, "catalogs") : void 0;
44
+ const catalogNode = topCatalog ?? workspacesCatalog;
45
+ const catalogsNode = topCatalogs ?? workspacesCatalogs;
46
+ const entries = /* @__PURE__ */ new Map();
47
+ if (catalogNode?.value?.type === "JSONObjectExpression") for (const property of catalogNode.value.properties || []) entries.set(`${String(property.key.value)}:default`, property);
48
+ if (catalogsNode?.value?.type === "JSONObjectExpression") for (const named of catalogsNode.value.properties || []) {
49
+ if (named.value.type !== "JSONObjectExpression") continue;
50
+ const catalogName = String(named.key.value);
51
+ for (const property of named.value.properties || []) entries.set(`${String(property.key.value)}:${catalogName}`, property);
52
+ }
53
+ if (entries.size === 0) return {};
54
+ const packages = collectWorkspacePackageJsonPaths(workspace);
55
+ for (const path of packages) {
56
+ const pkg = JSON.parse(fs.readFileSync(path, "utf8"));
57
+ for (const field of FIELDS) {
58
+ const map = getObjectPath(pkg, field.split("."));
59
+ if (!map) continue;
60
+ for (const [name, value] of Object.entries(map)) {
61
+ if (typeof value !== "string" || !value.startsWith("catalog:")) continue;
62
+ const catalogName = value.slice(8) || "default";
63
+ entries.delete(`${name}:${catalogName}`);
64
+ }
65
+ }
66
+ }
67
+ if (entries.size > 0) for (const [key, property] of Array.from(entries.entries()).sort((a, b) => a[0].localeCompare(b[0]))) context.report({
68
+ node: property.value,
69
+ messageId: "unusedCatalogItem",
70
+ data: { catalogItem: key }
71
+ });
72
+ return {};
73
+ }
427
74
  });
428
-
75
+ //#endregion
429
76
  export { json_no_unused_catalog_item_default as default };
@@ -0,0 +1,11 @@
1
+ import { RuleModule } from "../helpers/create-rule.mjs";
2
+ //#region src/rules/json-valid-catalog.d.ts
3
+ type Options = [{
4
+ autoInsert?: boolean;
5
+ autoInsertDefaultSpecifier?: string;
6
+ autofix?: boolean;
7
+ fields?: string[];
8
+ }];
9
+ declare const _default: RuleModule<Options>;
10
+ //#endregion
11
+ export { _default as default };