tsl-dx 0.11.0 → 0.12.1

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 (2) hide show
  1. package/dist/index.js +44 -16
  2. package/package.json +5 -5
package/dist/index.js CHANGED
@@ -2,6 +2,14 @@ import { defineRule } from "tsl";
2
2
  import ts, { SyntaxKind } from "typescript";
3
3
  import { match } from "ts-pattern";
4
4
 
5
+ //#region src/utils/print-node.ts
6
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
7
+ const dummySourceFile = ts.createSourceFile("", "", ts.ScriptTarget.Latest);
8
+ function printNode(node) {
9
+ return printer.printNode(ts.EmitHint.Unspecified, node, dummySourceFile);
10
+ }
11
+
12
+ //#endregion
5
13
  //#region src/rules/no-duplicate-exports.ts
6
14
  const messages$3 = { default: (p) => `Duplicate export from module ${p.source}.` };
7
15
  function isReExportDeclaration(node) {
@@ -48,9 +56,18 @@ const noDuplicateExports = defineRule(() => {
48
56
  function buildSuggestions$1(existing, incoming) {
49
57
  switch (true) {
50
58
  case ts.isNamedExports(existing.exportClause) && ts.isNamedExports(incoming.exportClause): {
51
- const existingElements = existing.exportClause.elements.map((el) => el.getText());
52
- const incomingElements = incoming.exportClause.elements.map((el) => el.getText());
53
- const parts = Array.from(new Set([...existingElements, ...incomingElements]));
59
+ const seen = /* @__PURE__ */ new Set();
60
+ const elements = [];
61
+ for (const el of [...existing.exportClause.elements, ...incoming.exportClause.elements]) {
62
+ const text = el.getText();
63
+ if (seen.has(text)) continue;
64
+ seen.add(text);
65
+ elements.push(ts.factory.createExportSpecifier(el.isTypeOnly, el.propertyName != null ? ts.factory.createIdentifier(el.propertyName.text) : void 0, ts.factory.createIdentifier(el.name.text)));
66
+ }
67
+ const specifierText = existing.moduleSpecifier.getText();
68
+ const isSingleQuote = specifierText.startsWith("'");
69
+ const specifierValue = specifierText.slice(1, -1);
70
+ const exportDecl = ts.factory.createExportDeclaration(void 0, existing.isTypeOnly, ts.factory.createNamedExports(elements), ts.factory.createStringLiteral(specifierValue, isSingleQuote));
54
71
  return [{
55
72
  message: "Merge duplicate exports",
56
73
  changes: [{
@@ -59,7 +76,7 @@ function buildSuggestions$1(existing, incoming) {
59
76
  newText: ""
60
77
  }, {
61
78
  node: existing,
62
- newText: `export ${existing.isTypeOnly ? "type " : ""}{ ${parts.join(", ")} } from ${existing.moduleSpecifier.getText()};`
79
+ newText: printNode(exportDecl)
63
80
  }]
64
81
  }];
65
82
  }
@@ -106,13 +123,13 @@ const noDuplicateImports = defineRule(() => {
106
123
  defaultImport: node.importClause.name?.getText() ?? null,
107
124
  bindings: match(node.importClause.namedBindings).with({ kind: ts.SyntaxKind.NamedImports }, (nb) => ({
108
125
  kind: "named",
109
- imports: nb.elements.map((el) => el.getText())
126
+ elements: nb.elements
110
127
  })).with({ kind: ts.SyntaxKind.NamespaceImport }, (nb) => ({
111
128
  kind: "namespace",
112
129
  name: nb.name.getText()
113
130
  })).otherwise(() => ({
114
131
  kind: "named",
115
- imports: []
132
+ elements: []
116
133
  }))
117
134
  };
118
135
  const existingImports = ctx.data.imports.get(importKind);
@@ -135,12 +152,19 @@ function buildSuggestions(existing, incoming) {
135
152
  case existing.defaultImport != null && incoming.defaultImport != null && existing.defaultImport !== incoming.defaultImport: return [];
136
153
  default: break;
137
154
  }
138
- const parts = [];
139
155
  const defaultImport = existing.defaultImport ?? incoming.defaultImport;
140
- if (defaultImport != null) parts.push(defaultImport);
141
- const mergedImports = Array.from(new Set([...existing.bindings.imports, ...incoming.bindings.imports]));
142
- if (mergedImports.length > 0) parts.push(`{ ${mergedImports.join(", ")} }`);
143
- const importKindPrefix = incoming.kind === "value" ? "import" : "import type";
156
+ const seen = /* @__PURE__ */ new Set();
157
+ const elements = [];
158
+ for (const el of [...existing.bindings.elements, ...incoming.bindings.elements]) {
159
+ const text = el.getText();
160
+ if (seen.has(text)) continue;
161
+ seen.add(text);
162
+ elements.push(ts.factory.createImportSpecifier(el.isTypeOnly, el.propertyName ? ts.factory.createIdentifier(el.propertyName.text) : void 0, ts.factory.createIdentifier(el.name.text)));
163
+ }
164
+ const sourceText = existing.source;
165
+ const isSingleQuote = sourceText.startsWith("'");
166
+ const sourceValue = sourceText.slice(1, -1);
167
+ const importDecl = ts.factory.createImportDeclaration(void 0, ts.factory.createImportClause(incoming.kind === "type", defaultImport != null ? ts.factory.createIdentifier(defaultImport) : void 0, ts.factory.createNamedImports(elements)), ts.factory.createStringLiteral(sourceValue, isSingleQuote));
144
168
  return [{
145
169
  message: "Merge duplicate imports",
146
170
  changes: [{
@@ -149,7 +173,7 @@ function buildSuggestions(existing, incoming) {
149
173
  newText: ""
150
174
  }, {
151
175
  node: existing.node,
152
- newText: `${importKindPrefix} ${parts.join(", ")} from ${existing.source};`
176
+ newText: printNode(importDecl)
153
177
  }]
154
178
  }];
155
179
  }
@@ -184,7 +208,9 @@ const messages$1 = {
184
208
  */
185
209
  const noMultilineTemplateExpressionWithoutAutoDedent = defineRule((options) => {
186
210
  const dedentTagNames = options?.dedentTagNames ?? ["dedent"];
187
- const dedentTagImportCallback = options?.dedentTagImportCallback ?? ((name) => `import ${name} from "dedent";\n`);
211
+ const dedentTagImportCallback = options?.dedentTagImportCallback ?? ((name) => {
212
+ return printNode(ts.factory.createImportDeclaration(void 0, ts.factory.createImportClause(void 0, ts.factory.createIdentifier(name), void 0), ts.factory.createStringLiteral("dedent"))) + "\n";
213
+ });
188
214
  function getLine(node) {
189
215
  const sourceFile = node.getSourceFile();
190
216
  return [sourceFile.getLineAndCharacterOfPosition(node.getStart()).line, sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line];
@@ -244,6 +270,7 @@ const nullish = defineRule((options) => ({
244
270
  visitor: { BinaryExpression(ctx, node) {
245
271
  const newOperatorText = match(node.operatorToken.kind).with(SyntaxKind.EqualsEqualsEqualsToken, () => "==").with(SyntaxKind.ExclamationEqualsEqualsToken, () => "!=").otherwise(() => null);
246
272
  if (newOperatorText == null) return;
273
+ const operatorToken = newOperatorText === "==" ? ts.factory.createToken(ts.SyntaxKind.EqualsEqualsToken) : ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsToken);
247
274
  const offendingChild = [node.left, node.right].find((n) => {
248
275
  switch (n.kind) {
249
276
  case SyntaxKind.NullKeyword: return true;
@@ -252,17 +279,18 @@ const nullish = defineRule((options) => ({
252
279
  }
253
280
  });
254
281
  if (offendingChild == null) return;
282
+ const nullNode = ts.factory.createNull();
255
283
  ctx.report({
256
284
  message: messages.default({ op: newOperatorText }),
257
285
  node,
258
286
  suggestions: [{
259
- message: messages.replace({ expr: offendingChild === node.left ? `null ${newOperatorText} ${node.right.getText()}` : `${node.left.getText()} ${newOperatorText} null` }),
287
+ message: messages.replace({ expr: printNode(offendingChild === node.left ? ts.factory.createBinaryExpression(nullNode, operatorToken, node.right) : ts.factory.createBinaryExpression(node.left, operatorToken, nullNode)) }),
260
288
  changes: [{
261
289
  node: node.operatorToken,
262
- newText: newOperatorText
290
+ newText: printNode(operatorToken)
263
291
  }, {
264
292
  node: offendingChild,
265
- newText: "null"
293
+ newText: printNode(nullNode)
266
294
  }]
267
295
  }]
268
296
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsl-dx",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "A tsl plugin for better JavaScript/TypeScript DX.",
5
5
  "keywords": [
6
6
  "tsl",
@@ -26,11 +26,11 @@
26
26
  "devDependencies": {
27
27
  "@liautaud/typezod": "^2.0.0",
28
28
  "dedent": "^1.7.2",
29
- "tsdown": "^0.21.9",
29
+ "tsdown": "^0.21.10",
30
30
  "tsl": "^1.0.30",
31
- "vitest": "^4.1.4",
32
- "@local/configs": "0.0.0",
33
- "@local/eff": "0.2.9"
31
+ "vitest": "^4.1.5",
32
+ "@local/eff": "0.2.9",
33
+ "@local/configs": "0.0.0"
34
34
  },
35
35
  "peerDependencies": {
36
36
  "tsl": "^1.0.30",