@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
@@ -0,0 +1,14 @@
1
+ import { RuleModule } from "../helpers/create-rule.mjs";
2
+ //#region src/rules/json-enforce-catalog.d.ts
3
+ type Options = [{
4
+ allowedProtocols?: string[];
5
+ autofix?: boolean;
6
+ defaultCatalog?: string;
7
+ reuseExistingCatalog?: boolean;
8
+ conflicts?: "new-catalog" | "overrides" | "error";
9
+ fields?: string[];
10
+ ignore?: string[];
11
+ }];
12
+ declare const _default: RuleModule<Options>;
13
+ //#endregion
14
+ export { _default as default };
@@ -1,447 +1,105 @@
1
- import defu from 'defu';
2
- import fs from 'node:fs';
3
- import process from 'node:process';
4
- import { findUpSync } from 'find-up-simple';
5
- import { dirname, normalize } from 'pathe';
6
- import 'tinyglobby';
7
-
8
- // src/helpers/create-rule.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
-
272
- // src/helpers/iterate-dependencies.ts
273
- function getPackageJsonRootNode(context) {
274
- if (!context.filename.endsWith("package.json")) {
275
- return void 0;
276
- }
277
- const ast = context.sourceCode.ast;
278
- const root = ast.body[0];
279
- if (root?.expression?.type === "JSONObjectExpression") {
280
- return root.expression;
281
- }
282
- return void 0;
283
- }
284
- function* iterateDependencies(context, fields) {
285
- const root = getPackageJsonRootNode(context);
286
- if (!root) {
287
- return;
288
- }
289
- for (const fieldName of fields) {
290
- const path = fieldName.split(".");
291
- let node = root;
292
- for (const segment of path) {
293
- const item = node.properties?.find(
294
- (property) => property.key.type === "JSONLiteral" && property.key.value === segment
295
- );
296
- if (!item?.value || item.value.type !== "JSONObjectExpression") {
297
- node = void 0;
298
- break;
299
- }
300
- node = item.value;
301
- }
302
- if (!node || node === root || !node.properties) {
303
- continue;
304
- }
305
- for (const property of node.properties) {
306
- if (property.value.type !== "JSONLiteral" || property.key.type !== "JSONLiteral") {
307
- continue;
308
- }
309
- if (typeof property.value.value !== "string") {
310
- continue;
311
- }
312
- yield {
313
- packageName: String(property.key.value),
314
- specifier: String(property.value.value),
315
- property
316
- };
317
- }
318
- }
319
- }
320
-
321
- // src/rules/json-enforce-catalog.ts
322
- var RULE_NAME = "json-enforce-catalog";
323
- var DEFAULT_FIELDS = ["dependencies", "devDependencies"];
324
- var IGNORED_DEPENDENCIES = ["typescript"];
1
+ import { createRule } from "../helpers/create-rule.mjs";
2
+ import { getBunWorkspace } from "../helpers/bun-workspace.mjs";
3
+ import { iterateDependencies } from "../helpers/iterate-dependencies.mjs";
4
+ //#region src/rules/json-enforce-catalog.ts
5
+ const RULE_NAME = "json-enforce-catalog";
6
+ const DEFAULT_FIELDS = ["dependencies", "devDependencies"];
7
+ const IGNORED_DEPENDENCIES = ["typescript"];
325
8
  var json_enforce_catalog_default = createRule({
326
- name: RULE_NAME,
327
- meta: {
328
- type: "layout",
329
- docs: {
330
- description: 'Enforce using "catalog:" in `package.json` and write versions to the Bun workspace root catalog'
331
- },
332
- fixable: "code",
333
- schema: [
334
- {
335
- type: "object",
336
- properties: {
337
- allowedProtocols: {
338
- type: "array",
339
- description: "Allowed protocols in specifier to not be converted to catalog",
340
- items: { type: "string" }
341
- },
342
- autofix: {
343
- type: "boolean",
344
- description: "Whether to autofix the linting error",
345
- default: true
346
- },
347
- defaultCatalog: {
348
- type: "string",
349
- description: "Default catalog to use when moving version to catalog with autofix"
350
- },
351
- reuseExistingCatalog: {
352
- type: "boolean",
353
- description: "Whether to reuse existing catalog when moving version to catalog with autofix",
354
- default: true
355
- },
356
- conflicts: {
357
- type: "string",
358
- description: "Strategy to handle conflicts when adding packages to catalogs",
359
- enum: ["new-catalog", "overrides", "error"],
360
- default: "new-catalog"
361
- },
362
- fields: {
363
- type: "array",
364
- description: "Fields to check for catalog",
365
- items: { type: "string" },
366
- default: DEFAULT_FIELDS
367
- },
368
- ignore: {
369
- type: "array",
370
- description: "A list of dependencies to ignore",
371
- items: { type: "string" },
372
- default: IGNORED_DEPENDENCIES
373
- }
374
- },
375
- additionalProperties: false
376
- }
377
- ],
378
- messages: {
379
- expectCatalog: 'Expect to use catalog instead of plain specifier, got "{{specifier}}" for package "{{packageName}}".'
380
- }
381
- },
382
- defaultOptions: [{}],
383
- create(context, [options]) {
384
- const {
385
- allowedProtocols = ["workspace", "link", "file"],
386
- defaultCatalog = "default",
387
- autofix = true,
388
- reuseExistingCatalog = true,
389
- conflicts = "new-catalog",
390
- fields = DEFAULT_FIELDS,
391
- ignore = IGNORED_DEPENDENCIES
392
- } = options || {};
393
- for (const { packageName, specifier, property } of iterateDependencies(
394
- context,
395
- fields
396
- )) {
397
- if (ignore?.some((name) => name === packageName)) {
398
- continue;
399
- }
400
- if (specifier.startsWith("catalog:")) {
401
- continue;
402
- }
403
- if (allowedProtocols?.some((protocol) => specifier.startsWith(protocol))) {
404
- continue;
405
- }
406
- let workspace2;
407
- try {
408
- workspace2 = getBunWorkspace();
409
- } catch {
410
- return {};
411
- }
412
- let targetCatalog = reuseExistingCatalog ? workspace2.getPackageCatalogs(packageName)[0] || defaultCatalog : defaultCatalog;
413
- const resolvedConflicts = workspace2.hasSpecifierConflicts(
414
- targetCatalog,
415
- packageName,
416
- specifier
417
- );
418
- let shouldFix = autofix;
419
- if (conflicts === "error" && resolvedConflicts.conflicts) {
420
- shouldFix = false;
421
- }
422
- if (conflicts === "new-catalog" && resolvedConflicts.conflicts) {
423
- targetCatalog = resolvedConflicts.newCatalogName;
424
- }
425
- context.report({
426
- node: property.value,
427
- messageId: "expectCatalog",
428
- data: {
429
- specifier,
430
- packageName
431
- },
432
- fix: shouldFix ? (fixer) => {
433
- workspace2.queueChange(() => {
434
- workspace2.setPackage(targetCatalog, packageName, specifier);
435
- });
436
- return fixer.replaceText(
437
- property.value,
438
- targetCatalog === "default" ? JSON.stringify("catalog:") : JSON.stringify(`catalog:${targetCatalog}`)
439
- );
440
- } : void 0
441
- });
442
- }
443
- return {};
444
- }
9
+ name: RULE_NAME,
10
+ meta: {
11
+ type: "layout",
12
+ docs: { description: "Enforce using \"catalog:\" in `package.json` and write versions to the Bun workspace root catalog" },
13
+ fixable: "code",
14
+ schema: [{
15
+ type: "object",
16
+ properties: {
17
+ allowedProtocols: {
18
+ type: "array",
19
+ description: "Allowed protocols in specifier to not be converted to catalog",
20
+ items: { type: "string" }
21
+ },
22
+ autofix: {
23
+ type: "boolean",
24
+ description: "Whether to autofix the linting error",
25
+ default: true
26
+ },
27
+ defaultCatalog: {
28
+ type: "string",
29
+ description: "Default catalog to use when moving version to catalog with autofix"
30
+ },
31
+ reuseExistingCatalog: {
32
+ type: "boolean",
33
+ description: "Whether to reuse existing catalog when moving version to catalog with autofix",
34
+ default: true
35
+ },
36
+ conflicts: {
37
+ type: "string",
38
+ description: "Strategy to handle conflicts when adding packages to catalogs",
39
+ enum: [
40
+ "new-catalog",
41
+ "overrides",
42
+ "error"
43
+ ],
44
+ default: "new-catalog"
45
+ },
46
+ fields: {
47
+ type: "array",
48
+ description: "Fields to check for catalog",
49
+ items: { type: "string" },
50
+ default: DEFAULT_FIELDS
51
+ },
52
+ ignore: {
53
+ type: "array",
54
+ description: "A list of dependencies to ignore",
55
+ items: { type: "string" },
56
+ default: IGNORED_DEPENDENCIES
57
+ }
58
+ },
59
+ additionalProperties: false
60
+ }],
61
+ messages: { expectCatalog: "Expect to use catalog instead of plain specifier, got \"{{specifier}}\" for package \"{{packageName}}\"." }
62
+ },
63
+ defaultOptions: [{}],
64
+ create(context, [options]) {
65
+ const { allowedProtocols = [
66
+ "workspace",
67
+ "link",
68
+ "file"
69
+ ], defaultCatalog = "default", autofix = true, reuseExistingCatalog = true, conflicts = "new-catalog", fields = DEFAULT_FIELDS, ignore = IGNORED_DEPENDENCIES } = options || {};
70
+ for (const { packageName, specifier, property } of iterateDependencies(context, fields)) {
71
+ if (ignore?.some((name) => name === packageName)) continue;
72
+ if (specifier.startsWith("catalog:")) continue;
73
+ if (allowedProtocols?.some((protocol) => specifier.startsWith(protocol))) continue;
74
+ let workspace;
75
+ try {
76
+ workspace = getBunWorkspace();
77
+ } catch {
78
+ return {};
79
+ }
80
+ let targetCatalog = reuseExistingCatalog ? workspace.getPackageCatalogs(packageName)[0] || defaultCatalog : defaultCatalog;
81
+ const resolvedConflicts = workspace.hasSpecifierConflicts(targetCatalog, packageName, specifier);
82
+ let shouldFix = autofix;
83
+ if (conflicts === "error" && resolvedConflicts.conflicts) shouldFix = false;
84
+ if (conflicts === "new-catalog" && resolvedConflicts.conflicts) targetCatalog = resolvedConflicts.newCatalogName;
85
+ if (conflicts === "overrides") {}
86
+ context.report({
87
+ node: property.value,
88
+ messageId: "expectCatalog",
89
+ data: {
90
+ specifier,
91
+ packageName
92
+ },
93
+ fix: shouldFix ? (fixer) => {
94
+ workspace.queueChange(() => {
95
+ workspace.setPackage(targetCatalog, packageName, specifier);
96
+ });
97
+ return fixer.replaceText(property.value, targetCatalog === "default" ? JSON.stringify("catalog:") : JSON.stringify(`catalog:${targetCatalog}`));
98
+ } : void 0
99
+ });
100
+ }
101
+ return {};
102
+ }
445
103
  });
446
-
104
+ //#endregion
447
105
  export { json_enforce_catalog_default as default };
@@ -0,0 +1,8 @@
1
+ import { RuleModule } from "../helpers/create-rule.mjs";
2
+ //#region src/rules/json-no-duplicate-catalog-item.d.ts
3
+ type Options = [{
4
+ allow?: string[];
5
+ }];
6
+ declare const _default: RuleModule<Options>;
7
+ //#endregion
8
+ export { _default as default };