tsl-dx 0.5.0 → 0.5.2

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
@@ -1,4 +1,4 @@
1
- import * as tsl0 from "tsl";
1
+ import * as tsl from "tsl";
2
2
 
3
3
  //#region src/rules/no-duplicate-exports.d.ts
4
4
  /**
@@ -17,7 +17,7 @@ import * as tsl0 from "tsl";
17
17
  * export { A, B } from 'module';
18
18
  * ```
19
19
  */
20
- declare const noDuplicateExports: (options?: "off" | undefined) => tsl0.Rule<unknown>;
20
+ declare const noDuplicateExports: (options?: "off" | undefined) => tsl.Rule<unknown>;
21
21
  //#endregion
22
22
  //#region src/rules/no-duplicate-imports.d.ts
23
23
  /**
@@ -36,16 +36,12 @@ declare const noDuplicateExports: (options?: "off" | undefined) => tsl0.Rule<unk
36
36
  * import { A, B } from 'module';
37
37
  * ```
38
38
  */
39
- declare const noDuplicateImports: (options?: "off" | undefined) => tsl0.Rule<unknown>;
39
+ declare const noDuplicateImports: (options?: "off" | undefined) => tsl.Rule<unknown>;
40
40
  //#endregion
41
- //#region src/rules/nullish-comparison.d.ts
42
- /**
43
- * Enforces the use of '==' and '!=' for nullish comparisons (i.e., comparisons to null or undefined)
44
- *
45
- * Rationale:
46
- * In TypeScript, using '==' and '!=' for nullish comparisons is a common practice because it checks for both null and undefined values
47
- * This rule promotes consistency in codebases by ensuring that developers use the appropriate operators for nullish checks
48
- */
49
- declare const nullishComparison: (options?: "off" | undefined) => tsl0.Rule<unknown>;
41
+ //#region src/rules/no-multiline-template-expressions-without-auto-dedent.d.ts
42
+ declare const noMultilineTemplateExpressionsWithoutAutoDedent: (options?: "off" | undefined) => tsl.Rule<unknown>;
43
+ //#endregion
44
+ //#region src/rules/nullish.d.ts
45
+ declare const nullish: (options?: Record<string, unknown> | "off" | undefined) => tsl.Rule<unknown>;
50
46
  //#endregion
51
- export { noDuplicateExports, noDuplicateImports, nullishComparison };
47
+ export { noDuplicateExports, noDuplicateImports, noMultilineTemplateExpressionsWithoutAutoDedent, nullish };
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import ts, { SyntaxKind } from "typescript";
3
3
  import { P, match } from "ts-pattern";
4
4
 
5
5
  //#region src/rules/no-duplicate-exports.ts
6
- const messages$2 = { default: (p) => `Duplicate export from module ${p.source}.` };
6
+ const messages$3 = { default: (p) => `Duplicate export from module ${p.source}.` };
7
7
  function isReExportDeclaration(node) {
8
8
  return node.exportClause != null && node.moduleSpecifier != null;
9
9
  }
@@ -36,7 +36,7 @@ const noDuplicateExports = defineRule(() => {
36
36
  if (duplicateExport != null) {
37
37
  ctx.report({
38
38
  node,
39
- message: messages$2.default({ source }),
39
+ message: messages$3.default({ source }),
40
40
  suggestions: buildSuggestions(duplicateExport, node)
41
41
  });
42
42
  return;
@@ -187,7 +187,7 @@ const compose = dual(2, (ab, bc) => (a) => bc(ab(a)));
187
187
 
188
188
  //#endregion
189
189
  //#region src/rules/no-duplicate-imports.ts
190
- const messages$1 = { default: (p) => `Duplicate import from module ${p.source}.` };
190
+ const messages$2 = { default: (p) => `Duplicate import from module ${p.source}.` };
191
191
  /**
192
192
  * Rule to detect and merge duplicate `import from` statements from the same module.
193
193
  *
@@ -228,7 +228,7 @@ const noDuplicateImports = defineRule(() => {
228
228
  if (duplicateImport != null) {
229
229
  ctx.report({
230
230
  node,
231
- message: messages$1.default({ source: importInfo.source }),
231
+ message: messages$2.default({ source: importInfo.source }),
232
232
  suggestions: importKind > 1 ? [] : [{
233
233
  message: "Merge duplicate imports",
234
234
  changes: [{
@@ -269,47 +269,87 @@ function buildMergedImport(a, b) {
269
269
  }
270
270
 
271
271
  //#endregion
272
- //#region src/rules/nullish-comparison.ts
273
- const messages = {
274
- default: (p) => `Use '${p.op}' for nullish comparison.`,
275
- replace: (p) => `Replace with '${p.expr}'.`
276
- };
277
- /**
278
- * Enforces the use of '==' and '!=' for nullish comparisons (i.e., comparisons to null or undefined)
279
- *
280
- * Rationale:
281
- * In TypeScript, using '==' and '!=' for nullish comparisons is a common practice because it checks for both null and undefined values
282
- * This rule promotes consistency in codebases by ensuring that developers use the appropriate operators for nullish checks
283
- */
284
- const nullishComparison = defineRule(() => ({
285
- name: "dx/nullish-comparison",
286
- visitor: { BinaryExpression(context, node) {
287
- const newOperatorText = match(node.operatorToken.kind).with(SyntaxKind.EqualsEqualsEqualsToken, () => "==").with(SyntaxKind.ExclamationEqualsEqualsToken, () => "!=").otherwise(() => null);
288
- if (newOperatorText == null) return;
289
- const offendingChild = [node.left, node.right].find((n) => {
290
- switch (n.kind) {
291
- case SyntaxKind.NullKeyword: return true;
292
- case SyntaxKind.Identifier: return n.escapedText === "undefined";
293
- default: return false;
294
- }
295
- });
296
- if (offendingChild == null) return;
297
- context.report({
298
- message: messages.default({ op: newOperatorText }),
272
+ //#region src/utils/source-file.ts
273
+ function getLine(node) {
274
+ const sourceFile = node.getSourceFile();
275
+ return [sourceFile.getLineAndCharacterOfPosition(node.getStart()).line, sourceFile.getLineAndCharacterOfPosition(node.getEnd()).line];
276
+ }
277
+
278
+ //#endregion
279
+ //#region src/rules/no-multiline-template-expressions-without-auto-dedent.ts
280
+ const messages$1 = { default: () => `Avoid using multiline template expressions without auto-dedent` };
281
+ const noMultilineTemplateExpressionsWithoutAutoDedent = defineRule(() => ({
282
+ name: "dx/no-multiline-template-expressions-without-auto-dedent",
283
+ visitor: { NoSubstitutionTemplateLiteral(ctx, node) {
284
+ if (node.parent.kind === SyntaxKind.TaggedTemplateExpression) return;
285
+ const [startLine, endLine] = getLine(node);
286
+ if (startLine !== endLine) ctx.report({
299
287
  node,
300
- suggestions: [{
301
- message: messages.replace({ expr: offendingChild === node.left ? `null ${newOperatorText} ${node.right.getText()}` : `${node.left.getText()} ${newOperatorText} null` }),
302
- changes: [{
303
- node: node.operatorToken,
304
- newText: newOperatorText
305
- }, {
306
- node: offendingChild,
307
- newText: "null"
308
- }]
309
- }]
288
+ message: messages$1.default()
310
289
  });
311
290
  } }
312
291
  }));
313
292
 
314
293
  //#endregion
315
- export { noDuplicateExports, noDuplicateImports, nullishComparison };
294
+ //#region src/rules/nullish.ts
295
+ const messages = {
296
+ useUnitForUndefined: "Use 'unit' instead of 'undefined'.",
297
+ useLooseNullishComparison: (p) => `Use '${p.op}' for nullish comparison.`
298
+ };
299
+ const suggestions = { replaceWithExpression: (p) => `Replace with '${p.expr}'.` };
300
+ const nullish = defineRule((options) => ({
301
+ name: "dx/nullish",
302
+ createData(ctx) {
303
+ return { runtimeLibrary: options?.runtimeLibrary ?? "@local/eff" };
304
+ },
305
+ visitor: {
306
+ UndefinedKeyword(ctx, node) {
307
+ if (node.getSourceFile().isDeclarationFile) return;
308
+ ctx.report({
309
+ node,
310
+ message: messages.useUnitForUndefined,
311
+ suggestions: [{
312
+ message: suggestions.replaceWithExpression({ expr: "unit" }),
313
+ changes: [{
314
+ node,
315
+ newText: "unit"
316
+ }, {
317
+ start: 0,
318
+ end: 0,
319
+ newText: `import { unit } from '${ctx.data.runtimeLibrary}';\n`
320
+ }]
321
+ }]
322
+ });
323
+ },
324
+ BinaryExpression(ctx, node) {
325
+ if (node.getSourceFile().isDeclarationFile) return;
326
+ const newOperatorText = match(node.operatorToken.kind).with(SyntaxKind.EqualsEqualsEqualsToken, () => "==").with(SyntaxKind.ExclamationEqualsEqualsToken, () => "!=").otherwise(() => null);
327
+ if (newOperatorText == null) return;
328
+ const offendingChild = [node.left, node.right].find((n) => {
329
+ switch (n.kind) {
330
+ case SyntaxKind.NullKeyword: return true;
331
+ case SyntaxKind.Identifier: return n.escapedText === "undefined";
332
+ default: return false;
333
+ }
334
+ });
335
+ if (offendingChild == null) return;
336
+ ctx.report({
337
+ message: messages.useLooseNullishComparison({ op: newOperatorText }),
338
+ node,
339
+ suggestions: [{
340
+ message: suggestions.replaceWithExpression({ expr: offendingChild === node.left ? `null ${newOperatorText} ${node.right.getText()}` : `${node.left.getText()} ${newOperatorText} null` }),
341
+ changes: [{
342
+ node: node.operatorToken,
343
+ newText: newOperatorText
344
+ }, {
345
+ node: offendingChild,
346
+ newText: "null"
347
+ }]
348
+ }]
349
+ });
350
+ }
351
+ }
352
+ }));
353
+
354
+ //#endregion
355
+ export { noDuplicateExports, noDuplicateImports, noMultilineTemplateExpressionsWithoutAutoDedent, nullish };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsl-dx",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "description": "A tsl plugin for better JavaScript/TypeScript DX.",
5
5
  "keywords": [
6
6
  "tsl",
@@ -25,7 +25,7 @@
25
25
  },
26
26
  "devDependencies": {
27
27
  "dedent": "^1.7.1",
28
- "tsdown": "^0.20.1",
28
+ "tsdown": "^0.20.3",
29
29
  "tsl": "^1.0.28",
30
30
  "vitest": "^4.0.18",
31
31
  "@local/configs": "0.0.0",