tsl-dx 0.6.1 → 0.7.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.
package/dist/index.d.ts CHANGED
@@ -39,9 +39,55 @@ declare const noDuplicateExports: (options?: "off" | undefined) => tsl.Rule<unkn
39
39
  declare const noDuplicateImports: (options?: "off" | undefined) => tsl.Rule<unknown>;
40
40
  //#endregion
41
41
  //#region src/rules/no-multiline-template-expression-without-auto-dedent.d.ts
42
- declare const noMultilineTemplateExpressionWithoutAutoDedent: (options?: "off" | undefined) => tsl.Rule<unknown>;
42
+ type noMultilineTemplateExpressionWithoutAutoDedentOptions = {
43
+ dedentTagNames?: string[];
44
+ dedentTagImportCallback?: (name: string) => string;
45
+ };
46
+ /**
47
+ * Rule to enforce the use of a dedent tag for multiline template expressions.
48
+ *
49
+ * @example
50
+ *
51
+ * ```ts
52
+ * // Incorrect
53
+ * const message = `
54
+ * Hello
55
+ * World
56
+ * `;
57
+ * ```
58
+ *
59
+ * ```ts
60
+ * // Correct
61
+ * import dedent from "dedent";
62
+ * const message = dedent`
63
+ * Hello
64
+ * World
65
+ * `;
66
+ * ```
67
+ */
68
+ declare const noMultilineTemplateExpressionWithoutAutoDedent: (options?: "off" | noMultilineTemplateExpressionWithoutAutoDedentOptions | undefined) => tsl.Rule<unknown>;
43
69
  //#endregion
44
70
  //#region src/rules/nullish.d.ts
45
- declare const nullish: (options?: Record<string, unknown> | "off" | undefined) => tsl.Rule<unknown>;
71
+ type nullishOptions = {
72
+ runtimeLibrary?: string;
73
+ };
74
+ /**
75
+ * Rule to enforce the use of `unit` instead of `undefined` and loose equality for nullish checks.
76
+ *
77
+ * @example
78
+ *
79
+ * ```ts
80
+ * // Incorrect
81
+ * let x = undefined;
82
+ * if (x === undefined) { }
83
+ * ```
84
+ *
85
+ * ```ts
86
+ * // Correct
87
+ * let x = unit;
88
+ * if (x == null) { }
89
+ * ```
90
+ */
91
+ declare const nullish: (options?: "off" | nullishOptions | undefined) => tsl.Rule<unknown>;
46
92
  //#endregion
47
93
  export { noDuplicateExports, noDuplicateImports, noMultilineTemplateExpressionWithoutAutoDedent, nullish };
package/dist/index.js CHANGED
@@ -170,6 +170,9 @@ const dual = function(arity, body) {
170
170
  * Composes two functions, `ab` and `bc` into a single function that takes in an argument `a` of type `A` and returns a result of type `C`.
171
171
  * The result is obtained by first applying the `ab` function to `a` and then applying the `bc` function to the result of `ab`.
172
172
  *
173
+ * @param self - The first function to apply (or the composed function in data-last style).
174
+ * @param bc - The second function to apply.
175
+ * @returns A composed function that applies both functions in sequence.
173
176
  * @example
174
177
  * ```ts
175
178
  * import * as assert from "node:assert"
@@ -277,26 +280,85 @@ function getLine(node) {
277
280
 
278
281
  //#endregion
279
282
  //#region src/rules/no-multiline-template-expression-without-auto-dedent.ts
280
- const messages$1 = { default: () => `Avoid using multiline template expression without auto-dedent` };
281
- const noMultilineTemplateExpressionWithoutAutoDedent = defineRule(() => ({
282
- name: "dx/no-multiline-template-expression-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({
287
- node,
288
- message: messages$1.default()
289
- });
290
- } }
291
- }));
283
+ const messages$1 = {
284
+ useDedentTag: () => "Use a dedent tag to auto-dedent this template expression's content.",
285
+ addDedentTag: (p) => `Add a/an '${p.name}' tag to this template expression to auto-dedent its content.`
286
+ };
287
+ /**
288
+ * Rule to enforce the use of a dedent tag for multiline template expressions.
289
+ *
290
+ * @example
291
+ *
292
+ * ```ts
293
+ * // Incorrect
294
+ * const message = `
295
+ * Hello
296
+ * World
297
+ * `;
298
+ * ```
299
+ *
300
+ * ```ts
301
+ * // Correct
302
+ * import dedent from "dedent";
303
+ * const message = dedent`
304
+ * Hello
305
+ * World
306
+ * `;
307
+ * ```
308
+ */
309
+ const noMultilineTemplateExpressionWithoutAutoDedent = defineRule((options) => {
310
+ const dedentTagNames = options?.dedentTagNames ?? ["dedent"];
311
+ const dedentTagImportCallback = options?.dedentTagImportCallback ?? ((name) => `import ${name} from "dedent";\n`);
312
+ return {
313
+ name: "dx/no-multiline-template-expression-without-auto-dedent",
314
+ visitor: { NoSubstitutionTemplateLiteral(ctx, node) {
315
+ if (node.parent.kind === SyntaxKind.TaggedTemplateExpression) return;
316
+ const [startLine, endLine] = getLine(node);
317
+ if (startLine !== endLine) ctx.report({
318
+ node,
319
+ message: messages$1.useDedentTag(),
320
+ suggestions: dedentTagNames.map((name) => {
321
+ return {
322
+ message: messages$1.addDedentTag({ name }),
323
+ changes: [{
324
+ start: 0,
325
+ end: 0,
326
+ newText: dedentTagImportCallback(name)
327
+ }, {
328
+ node,
329
+ newText: `${name}${node.getFullText()}`
330
+ }]
331
+ };
332
+ })
333
+ });
334
+ } }
335
+ };
336
+ });
292
337
 
293
338
  //#endregion
294
339
  //#region src/rules/nullish.ts
295
340
  const messages = {
296
341
  useUnitForUndefined: "Use 'unit' instead of 'undefined'.",
297
- useLooseNullishComparison: (p) => `Use '${p.op}' for nullish comparison.`
342
+ useLooseNullishComparison: (p) => `Use '${p.op}' for nullish comparison.`,
343
+ replaceWithExpression: (p) => `Replace with '${p.expr}'.`
298
344
  };
299
- const suggestions = { replaceWithExpression: (p) => `Replace with '${p.expr}'.` };
345
+ /**
346
+ * Rule to enforce the use of `unit` instead of `undefined` and loose equality for nullish checks.
347
+ *
348
+ * @example
349
+ *
350
+ * ```ts
351
+ * // Incorrect
352
+ * let x = undefined;
353
+ * if (x === undefined) { }
354
+ * ```
355
+ *
356
+ * ```ts
357
+ * // Correct
358
+ * let x = unit;
359
+ * if (x == null) { }
360
+ * ```
361
+ */
300
362
  const nullish = defineRule((options) => ({
301
363
  name: "dx/nullish",
302
364
  createData(ctx) {
@@ -304,44 +366,41 @@ const nullish = defineRule((options) => ({
304
366
  },
305
367
  visitor: {
306
368
  Identifier(ctx, node) {
307
- if (node.getSourceFile().isDeclarationFile) return;
308
369
  if (node.parent.kind === SyntaxKind.BinaryExpression || node.text !== "undefined") return;
309
370
  ctx.report({
310
371
  node,
311
372
  message: messages.useUnitForUndefined,
312
373
  suggestions: [{
313
- message: suggestions.replaceWithExpression({ expr: "unit" }),
374
+ message: messages.replaceWithExpression({ expr: "unit" }),
314
375
  changes: [{
315
- node,
316
- newText: "unit"
317
- }, {
318
376
  start: 0,
319
377
  end: 0,
320
378
  newText: `import { unit } from '${ctx.data.runtimeLibrary}';\n`
379
+ }, {
380
+ node,
381
+ newText: "unit"
321
382
  }]
322
383
  }]
323
384
  });
324
385
  },
325
386
  UndefinedKeyword(ctx, node) {
326
- if (node.getSourceFile().isDeclarationFile) return;
327
387
  ctx.report({
328
388
  node,
329
389
  message: messages.useUnitForUndefined,
330
390
  suggestions: [{
331
- message: suggestions.replaceWithExpression({ expr: "unit" }),
391
+ message: messages.replaceWithExpression({ expr: "unit" }),
332
392
  changes: [{
333
- node,
334
- newText: "unit"
335
- }, {
336
393
  start: 0,
337
394
  end: 0,
338
395
  newText: `import type { unit } from '${ctx.data.runtimeLibrary}';\n`
396
+ }, {
397
+ node,
398
+ newText: "unit"
339
399
  }]
340
400
  }]
341
401
  });
342
402
  },
343
403
  BinaryExpression(ctx, node) {
344
- if (node.getSourceFile().isDeclarationFile) return;
345
404
  const newOperatorText = match(node.operatorToken.kind).with(SyntaxKind.EqualsEqualsEqualsToken, () => "==").with(SyntaxKind.ExclamationEqualsEqualsToken, () => "!=").otherwise(() => null);
346
405
  if (newOperatorText == null) return;
347
406
  const offendingChild = [node.left, node.right].find((n) => {
@@ -356,7 +415,7 @@ const nullish = defineRule((options) => ({
356
415
  message: messages.useLooseNullishComparison({ op: newOperatorText }),
357
416
  node,
358
417
  suggestions: [{
359
- message: suggestions.replaceWithExpression({ expr: offendingChild === node.left ? `null ${newOperatorText} ${node.right.getText()}` : `${node.left.getText()} ${newOperatorText} null` }),
418
+ message: messages.replaceWithExpression({ expr: offendingChild === node.left ? `null ${newOperatorText} ${node.right.getText()}` : `${node.left.getText()} ${newOperatorText} null` }),
360
419
  changes: [{
361
420
  node: node.operatorToken,
362
421
  newText: newOperatorText
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tsl-dx",
3
- "version": "0.6.1",
3
+ "version": "0.7.1",
4
4
  "description": "A tsl plugin for better JavaScript/TypeScript DX.",
5
5
  "keywords": [
6
6
  "tsl",
@@ -26,7 +26,7 @@
26
26
  "devDependencies": {
27
27
  "dedent": "^1.7.1",
28
28
  "tsdown": "^0.20.3",
29
- "tsl": "^1.0.28",
29
+ "tsl": "^1.0.29",
30
30
  "vitest": "^4.0.18",
31
31
  "@local/configs": "0.0.0",
32
32
  "@local/eff": "0.2.9"