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 +1 -3
- package/dist/index.js +45 -19
- package/package.json +5 -5
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
|
|
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
|
|
52
|
-
const
|
|
53
|
-
const
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
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:
|
|
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) =>
|
|
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
|
|
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 ?
|
|
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:
|
|
290
|
+
newText: printNode(operatorToken)
|
|
265
291
|
}, {
|
|
266
292
|
node: offendingChild,
|
|
267
|
-
newText:
|
|
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.
|
|
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.
|
|
29
|
+
"tsdown": "^0.21.9",
|
|
30
30
|
"tsl": "^1.0.30",
|
|
31
|
-
"vitest": "^4.1.
|
|
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.
|
|
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": "
|
|
45
|
+
"lint:publish": "publint",
|
|
46
46
|
"lint:ts": "tsl",
|
|
47
47
|
"test": "vitest"
|
|
48
48
|
}
|