tsl-dx 0.7.1 → 0.8.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/dist/index.d.ts CHANGED
@@ -88,6 +88,6 @@ type nullishOptions = {
88
88
  * if (x == null) { }
89
89
  * ```
90
90
  */
91
- declare const nullish: (options?: "off" | nullishOptions | undefined) => tsl.Rule<unknown>;
91
+ declare const nullish: (options?: nullishOptions | "off" | undefined) => tsl.Rule<unknown>;
92
92
  //#endregion
93
93
  export { noDuplicateExports, noDuplicateImports, noMultilineTemplateExpressionWithoutAutoDedent, nullish };
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { defineRule } from "tsl";
2
2
  import ts, { SyntaxKind } from "typescript";
3
- import { P, match } from "ts-pattern";
3
+ import { match } from "ts-pattern";
4
4
 
5
5
  //#region src/rules/no-duplicate-exports.ts
6
6
  const messages$3 = { default: (p) => `Duplicate export from module ${p.source}.` };
@@ -37,7 +37,7 @@ const noDuplicateExports = defineRule(() => {
37
37
  ctx.report({
38
38
  node,
39
39
  message: messages$3.default({ source }),
40
- suggestions: buildSuggestions(duplicateExport, node)
40
+ suggestions: buildSuggestions$1(duplicateExport, node)
41
41
  });
42
42
  return;
43
43
  }
@@ -45,7 +45,7 @@ const noDuplicateExports = defineRule(() => {
45
45
  } }
46
46
  };
47
47
  });
48
- function buildSuggestions(a, b) {
48
+ function buildSuggestions$1(a, b) {
49
49
  switch (true) {
50
50
  case ts.isNamedExports(a.exportClause) && ts.isNamedExports(b.exportClause): {
51
51
  const aElements = a.exportClause.elements.map((el) => el.getText());
@@ -69,10 +69,6 @@ function buildSuggestions(a, b) {
69
69
  //#endregion
70
70
  //#region ../../.pkgs/eff/dist/index.js
71
71
  /**
72
- * alias for `undefined`.
73
- */
74
- const unit = void 0;
75
- /**
76
72
  * Creates a function that can be used in a data-last (aka `pipe`able) or
77
73
  * data-first style.
78
74
  *
@@ -211,64 +207,63 @@ const noDuplicateImports = defineRule(() => {
211
207
  return {
212
208
  name: "dx/no-duplicate-imports",
213
209
  createData() {
214
- return { imports: [
215
- [],
216
- [],
217
- []
218
- ] };
210
+ return { imports: new Map([
211
+ ["value", []],
212
+ ["type", []],
213
+ ["defer", []]
214
+ ]) };
219
215
  },
220
216
  visitor: { ImportDeclaration(ctx, node) {
221
217
  if (node.importClause == null) return;
222
- const importKind = getImportKind(node);
218
+ const importKind = match(node.importClause.phaseModifier).with(ts.SyntaxKind.TypeKeyword, () => "type").with(ts.SyntaxKind.DeferKeyword, () => "defer").otherwise(() => "value");
223
219
  const importInfo = {
224
220
  node,
225
221
  source: node.moduleSpecifier.getText(),
226
222
  kind: importKind,
227
- ...decodeImportClause(node.importClause)
223
+ defaultImport: node.importClause.name?.getText(),
224
+ bindings: match(node.importClause.namedBindings).with({ kind: ts.SyntaxKind.NamedImports }, (nb) => ({
225
+ kind: "named",
226
+ imports: nb.elements.map((el) => el.getText())
227
+ })).with({ kind: ts.SyntaxKind.NamespaceImport }, (nb) => ({
228
+ kind: "namespace",
229
+ name: nb.name.getText()
230
+ })).otherwise(() => ({
231
+ kind: "named",
232
+ imports: []
233
+ }))
228
234
  };
229
- const existingImports = ctx.data.imports[importKind];
235
+ const existingImports = ctx.data.imports.get(importKind);
230
236
  const duplicateImport = existingImports.find((imp) => imp.source === importInfo.source);
231
- if (duplicateImport != null) {
232
- ctx.report({
233
- node,
234
- message: messages$2.default({ source: importInfo.source }),
235
- suggestions: importKind > 1 ? [] : [{
236
- message: "Merge duplicate imports",
237
- changes: [{
238
- node,
239
- newText: ""
240
- }, {
241
- node: duplicateImport.node,
242
- newText: buildMergedImport(duplicateImport, importInfo)
243
- }]
244
- }]
245
- });
237
+ if (duplicateImport == null) {
238
+ existingImports.push(importInfo);
246
239
  return;
247
240
  }
248
- existingImports.push(importInfo);
241
+ ctx.report({
242
+ node,
243
+ message: messages$2.default({ source: importInfo.source }),
244
+ suggestions: buildSuggestions(duplicateImport, importInfo)
245
+ });
249
246
  } }
250
247
  };
251
248
  });
252
- function getImportKind(node) {
253
- return match(node.importClause?.phaseModifier).with(P.nullish, () => 0).with(ts.SyntaxKind.TypeKeyword, () => 1).with(ts.SyntaxKind.DeferKeyword, () => 2).otherwise(() => 0);
254
- }
255
- function decodeImportClause(node) {
256
- const { name, namedBindings } = node;
257
- return {
258
- defaultImport: name?.getText(),
259
- namedImports: namedBindings != null && ts.isNamedImports(namedBindings) ? namedBindings.elements.map((el) => el.getText()) : [],
260
- namespaceImport: namedBindings != null && ts.isNamespaceImport(namedBindings) ? namedBindings.name.getText() : unit
261
- };
262
- }
263
- function buildMergedImport(a, b) {
249
+ function buildSuggestions(existing, incoming) {
250
+ if (incoming.kind === "defer" || incoming.bindings.kind === "namespace" || existing.bindings.kind === "namespace") return [];
264
251
  const parts = [];
265
- if (a.defaultImport != null) parts.push(a.defaultImport);
266
- else if (b.defaultImport != null) parts.push(b.defaultImport);
267
- if (a.namespaceImport != null) parts.push(`* as ${a.namespaceImport}`);
268
- else if (b.namespaceImport != null) parts.push(`* as ${b.namespaceImport}`);
269
- const namedImports = Array.from(new Set([...a.namedImports, ...b.namedImports]));
270
- if (namedImports.length > 0) parts.push(`{ ${namedImports.join(", ")} }`);
271
- return `${match(a.kind).with(0, () => "import").with(1, () => "import type").with(2, () => "import defer").exhaustive()} ${parts.join(", ")} from ${a.source};`;
252
+ const defaultImport = existing.defaultImport ?? incoming.defaultImport;
253
+ if (defaultImport != null) parts.push(defaultImport);
254
+ const mergedImports = Array.from(new Set([...existing.bindings.imports, ...incoming.bindings.imports]));
255
+ if (mergedImports.length > 0) parts.push(`{ ${mergedImports.join(", ")} }`);
256
+ const importKindPrefix = incoming.kind === "value" ? "import" : "import type";
257
+ return [{
258
+ message: "Merge duplicate imports",
259
+ changes: [{
260
+ node: incoming.node,
261
+ newText: ""
262
+ }, {
263
+ node: existing.node,
264
+ newText: `${importKindPrefix} ${parts.join(", ")} from ${existing.source};`
265
+ }]
266
+ }];
272
267
  }
273
268
 
274
269
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsl-dx",
3
- "version": "0.7.1",
3
+ "version": "0.8.0",
4
4
  "description": "A tsl plugin for better JavaScript/TypeScript DX.",
5
5
  "keywords": [
6
6
  "tsl",
@@ -24,6 +24,7 @@
24
24
  "ts-pattern": "^5.9.0"
25
25
  },
26
26
  "devDependencies": {
27
+ "@liautaud/typezod": "^2.0.0",
27
28
  "dedent": "^1.7.1",
28
29
  "tsdown": "^0.20.3",
29
30
  "tsl": "^1.0.29",