tsl-dx 0.10.3 → 0.12.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
@@ -72,19 +72,17 @@ type nullishOptions = {
72
72
  runtimeLibrary?: string;
73
73
  };
74
74
  /**
75
- * Rule to enforce the use of `unit` instead of `undefined` and loose equality for nullish checks.
75
+ * Rule to enforce the use of loose equality for nullish checks.
76
76
  *
77
77
  * @example
78
78
  *
79
79
  * ```ts
80
80
  * // Incorrect
81
- * let x = undefined;
82
81
  * if (x === undefined) { }
83
82
  * ```
84
83
  *
85
84
  * ```ts
86
85
  * // Correct
87
- * let x = unit;
88
86
  * if (x == null) { }
89
87
  * ```
90
88
  */
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];
@@ -222,19 +248,17 @@ const messages = {
222
248
  replace: (p) => `Replace with '${p.expr}'.`
223
249
  };
224
250
  /**
225
- * Rule to enforce the use of `unit` instead of `undefined` and loose equality for nullish checks.
251
+ * Rule to enforce the use of loose equality for nullish checks.
226
252
  *
227
253
  * @example
228
254
  *
229
255
  * ```ts
230
256
  * // Incorrect
231
- * let x = undefined;
232
257
  * if (x === undefined) { }
233
258
  * ```
234
259
  *
235
260
  * ```ts
236
261
  * // Correct
237
- * let x = unit;
238
262
  * if (x == null) { }
239
263
  * ```
240
264
  */
@@ -246,6 +270,7 @@ const nullish = defineRule((options) => ({
246
270
  visitor: { BinaryExpression(ctx, node) {
247
271
  const newOperatorText = match(node.operatorToken.kind).with(SyntaxKind.EqualsEqualsEqualsToken, () => "==").with(SyntaxKind.ExclamationEqualsEqualsToken, () => "!=").otherwise(() => null);
248
272
  if (newOperatorText == null) return;
273
+ const operatorToken = newOperatorText === "==" ? ts.factory.createToken(ts.SyntaxKind.EqualsEqualsToken) : ts.factory.createToken(ts.SyntaxKind.ExclamationEqualsToken);
249
274
  const offendingChild = [node.left, node.right].find((n) => {
250
275
  switch (n.kind) {
251
276
  case SyntaxKind.NullKeyword: return true;
@@ -254,17 +279,18 @@ const nullish = defineRule((options) => ({
254
279
  }
255
280
  });
256
281
  if (offendingChild == null) return;
282
+ const nullNode = ts.factory.createNull();
257
283
  ctx.report({
258
284
  message: messages.default({ op: newOperatorText }),
259
285
  node,
260
286
  suggestions: [{
261
- 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)) }),
262
288
  changes: [{
263
289
  node: node.operatorToken,
264
- newText: newOperatorText
290
+ newText: printNode(operatorToken)
265
291
  }, {
266
292
  node: offendingChild,
267
- newText: "null"
293
+ newText: printNode(nullNode)
268
294
  }]
269
295
  }]
270
296
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsl-dx",
3
- "version": "0.10.3",
3
+ "version": "0.12.0",
4
4
  "description": "A tsl plugin for better JavaScript/TypeScript DX.",
5
5
  "keywords": [
6
6
  "tsl",
@@ -26,14 +26,14 @@
26
26
  "devDependencies": {
27
27
  "@liautaud/typezod": "^2.0.0",
28
28
  "dedent": "^1.7.2",
29
- "tsdown": "^0.21.7",
29
+ "tsdown": "^0.21.9",
30
30
  "tsl": "^1.0.30",
31
- "vitest": "^4.1.2",
31
+ "vitest": "^4.1.5",
32
32
  "@local/configs": "0.0.0",
33
33
  "@local/eff": "0.2.9"
34
34
  },
35
35
  "peerDependencies": {
36
- "tsl": "^1.0.29",
36
+ "tsl": "^1.0.30",
37
37
  "typescript": "*"
38
38
  },
39
39
  "publishConfig": {
@@ -42,7 +42,7 @@
42
42
  "scripts": {
43
43
  "build": "tsdown --dts-resolve",
44
44
  "build:docs": "typedoc",
45
- "lint:publish": "pnpm publint",
45
+ "lint:publish": "publint",
46
46
  "lint:ts": "tsl",
47
47
  "test": "vitest"
48
48
  }