eslint-plugin-kirklin 1.1.0 → 1.3.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.cjs CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- const version = "1.1.0";
3
+ const version = "1.3.0";
4
4
 
5
5
  const hasDocs = [
6
6
  "consistent-list-newline",
@@ -49,6 +49,27 @@ function createRule({
49
49
  const createEslintRule = RuleCreator(
50
50
  (ruleName) => hasDocs.includes(ruleName) ? `${blobUrl}${ruleName}.md` : `${blobUrl}${ruleName}.test.ts`
51
51
  );
52
+ const _reFullWs = /^\s*$/;
53
+ function unindent(str) {
54
+ const lines = (typeof str === "string" ? str : str[0]).split("\n");
55
+ const whitespaceLines = lines.map((line) => _reFullWs.test(line));
56
+ const commonIndent = lines.reduce((min, line, idx) => {
57
+ if (whitespaceLines[idx]) {
58
+ return min;
59
+ }
60
+ const indent = line.match(/^\s*/)?.[0].length;
61
+ return indent === void 0 ? min : Math.min(min, indent);
62
+ }, Number.POSITIVE_INFINITY);
63
+ let emptyLinesHead = 0;
64
+ while (emptyLinesHead < lines.length && whitespaceLines[emptyLinesHead]) {
65
+ emptyLinesHead++;
66
+ }
67
+ let emptyLinesTail = 0;
68
+ while (emptyLinesTail < lines.length && whitespaceLines[lines.length - emptyLinesTail - 1]) {
69
+ emptyLinesTail++;
70
+ }
71
+ return lines.slice(emptyLinesHead, lines.length - emptyLinesTail).map((line) => line.slice(commonIndent)).join("\n");
72
+ }
52
73
 
53
74
  const RULE_NAME$6 = "if-newline";
54
75
  const ifNewline = createEslintRule({
@@ -351,7 +372,8 @@ const consistentListNewline = createEslintRule({
351
372
  TSTypeParameterDeclaration: { type: "boolean" },
352
373
  TSTypeParameterInstantiation: { type: "boolean" },
353
374
  ObjectPattern: { type: "boolean" },
354
- ArrayPattern: { type: "boolean" }
375
+ ArrayPattern: { type: "boolean" },
376
+ JSXOpeningElement: { type: "boolean" }
355
377
  },
356
378
  additionalProperties: false
357
379
  }],
@@ -372,7 +394,10 @@ const consistentListNewline = createEslintRule({
372
394
  if (items.length === 0) {
373
395
  return;
374
396
  }
375
- const startToken = context.sourceCode.getTokenBefore(items[0]);
397
+ let startToken = ["CallExpression", "NewExpression"].includes(node.type) ? void 0 : context.sourceCode.getFirstToken(node);
398
+ if (startToken?.type !== "Punctuator") {
399
+ startToken = context.sourceCode.getTokenBefore(items[0]);
400
+ }
376
401
  const endToken = context.sourceCode.getTokenAfter(items[items.length - 1]);
377
402
  const startLine = startToken.loc.start.line;
378
403
  if (startToken.loc.start.line === endToken.loc.end.line) {
@@ -400,16 +425,22 @@ const consistentListNewline = createEslintRule({
400
425
  });
401
426
  } else if (mode === "inline" && currentStart !== lastLine) {
402
427
  const lastItem2 = items[idx - 1];
403
- context.report({
404
- node: item,
405
- messageId: "shouldNotWrap",
406
- data: {
407
- name: node.type
408
- },
409
- *fix(fixer) {
410
- yield removeLines(fixer, lastItem2.range[1], item.range[0]);
411
- }
412
- });
428
+ if (context.sourceCode.getCommentsBefore(item).length > 0) {
429
+ return;
430
+ }
431
+ const content = context.sourceCode.text.slice(lastItem2.range[1], item.range[0]);
432
+ if (content.includes("\n")) {
433
+ context.report({
434
+ node: item,
435
+ messageId: "shouldNotWrap",
436
+ data: {
437
+ name: node.type
438
+ },
439
+ *fix(fixer) {
440
+ yield removeLines(fixer, lastItem2.range[1], item.range[0]);
441
+ }
442
+ });
443
+ }
413
444
  }
414
445
  lastLine = item.loc.end.line;
415
446
  });
@@ -434,6 +465,9 @@ const consistentListNewline = createEslintRule({
434
465
  if (items.length === 1 && items[0].loc.start.line !== items[1]?.loc.start.line) {
435
466
  return;
436
467
  }
468
+ if (context.sourceCode.getCommentsAfter(lastItem).length > 0) {
469
+ return;
470
+ }
437
471
  const content = context.sourceCode.text.slice(lastItem.range[1], endRange);
438
472
  if (content.includes("\n")) {
439
473
  context.report({
@@ -512,6 +546,12 @@ const consistentListNewline = createEslintRule({
512
546
  },
513
547
  ArrayPattern(node) {
514
548
  check(node, node.elements);
549
+ },
550
+ JSXOpeningElement(node) {
551
+ if (node.attributes.some((attr) => attr.loc.start.line !== attr.loc.end.line)) {
552
+ return;
553
+ }
554
+ check(node, node.attributes);
515
555
  }
516
556
  };
517
557
  Object.keys(options).forEach((key) => {
@@ -523,17 +563,93 @@ const consistentListNewline = createEslintRule({
523
563
  }
524
564
  });
525
565
 
566
+ const indentUnindent = createEslintRule({
567
+ name: "indent-unindent",
568
+ meta: {
569
+ type: "layout",
570
+ docs: {
571
+ description: "Enforce consistent indentation in `unindent` template tag"
572
+ },
573
+ fixable: "code",
574
+ schema: [
575
+ {
576
+ type: "object",
577
+ properties: {
578
+ indent: {
579
+ type: "number",
580
+ minimum: 0,
581
+ default: 2
582
+ },
583
+ tags: {
584
+ type: "array",
585
+ items: {
586
+ type: "string"
587
+ }
588
+ }
589
+ },
590
+ additionalProperties: false
591
+ }
592
+ ],
593
+ messages: {
594
+ "indent-unindent": "Consistent indentation in unindent tag"
595
+ }
596
+ },
597
+ defaultOptions: [{}],
598
+ create(context) {
599
+ const {
600
+ tags = ["$", "unindent", "unIndent"],
601
+ indent = 2
602
+ } = context.options?.[0] ?? {};
603
+ return {
604
+ TaggedTemplateExpression(node) {
605
+ const id = node.tag;
606
+ if (!id || id.type !== "Identifier") {
607
+ return;
608
+ }
609
+ if (!tags.includes(id.name)) {
610
+ return;
611
+ }
612
+ if (node.quasi.quasis.length !== 1) {
613
+ return;
614
+ }
615
+ const quasi = node.quasi.quasis[0];
616
+ const value = quasi.value.raw;
617
+ const lineStartIndex = context.sourceCode.getIndexFromLoc({
618
+ line: node.loc.start.line,
619
+ column: 0
620
+ });
621
+ const baseIndent = context.sourceCode.text.slice(lineStartIndex).match(/^\s*/)?.[0] ?? "";
622
+ const targetIndent = baseIndent + " ".repeat(indent);
623
+ const pure = unindent([value]);
624
+ let final = pure.split("\n").map((line) => targetIndent + line).join("\n");
625
+ final = `
626
+ ${final}
627
+ ${baseIndent}`;
628
+ if (final !== value) {
629
+ context.report({
630
+ node: quasi,
631
+ messageId: "indent-unindent",
632
+ fix: (fixer) => fixer.replaceText(quasi, `\`${final}\``)
633
+ });
634
+ }
635
+ }
636
+ };
637
+ }
638
+ });
639
+
526
640
  const plugin = {
527
641
  meta: {
528
642
  name: "kirklin",
529
643
  version
530
644
  },
645
+ // @keep-sorted
531
646
  rules: {
532
647
  "consistent-list-newline": consistentListNewline,
533
648
  "if-newline": ifNewline,
534
649
  "import-dedupe": importDedupe,
535
- "no-import-node-modules-by-path": noImportNodeModulesByPath,
650
+ "indent-unindent": indentUnindent,
536
651
  "no-import-dist": noImportDist,
652
+ "no-import-node-modules-by-path": noImportNodeModulesByPath,
537
653
  "no-ts-export-equal": noTsExportEqual,
538
654
  "top-level-function": topLevelFunction
539
655
  }
package/dist/index.d.cts CHANGED
@@ -4,6 +4,13 @@ interface RuleModule<T extends readonly unknown[]> extends Rule.RuleModule {
4
4
  defaultOptions: T;
5
5
  }
6
6
 
7
+ type Options$1 = [
8
+ {
9
+ indent?: number;
10
+ tags?: string[];
11
+ }
12
+ ];
13
+
7
14
  type Options = [
8
15
  {
9
16
  ArrayExpression?: boolean;
@@ -22,6 +29,7 @@ type Options = [
22
29
  TSTypeParameterInstantiation?: boolean;
23
30
  ObjectPattern?: boolean;
24
31
  ArrayPattern?: boolean;
32
+ JSXOpeningElement?: boolean;
25
33
  }
26
34
  ];
27
35
 
@@ -34,8 +42,9 @@ declare const plugin: {
34
42
  "consistent-list-newline": RuleModule<Options>;
35
43
  "if-newline": RuleModule<[]>;
36
44
  "import-dedupe": RuleModule<[]>;
37
- "no-import-node-modules-by-path": RuleModule<[]>;
45
+ "indent-unindent": RuleModule<Options$1>;
38
46
  "no-import-dist": RuleModule<[]>;
47
+ "no-import-node-modules-by-path": RuleModule<[]>;
39
48
  "no-ts-export-equal": RuleModule<[]>;
40
49
  "top-level-function": RuleModule<[]>;
41
50
  };
package/dist/index.d.mts CHANGED
@@ -4,6 +4,13 @@ interface RuleModule<T extends readonly unknown[]> extends Rule.RuleModule {
4
4
  defaultOptions: T;
5
5
  }
6
6
 
7
+ type Options$1 = [
8
+ {
9
+ indent?: number;
10
+ tags?: string[];
11
+ }
12
+ ];
13
+
7
14
  type Options = [
8
15
  {
9
16
  ArrayExpression?: boolean;
@@ -22,6 +29,7 @@ type Options = [
22
29
  TSTypeParameterInstantiation?: boolean;
23
30
  ObjectPattern?: boolean;
24
31
  ArrayPattern?: boolean;
32
+ JSXOpeningElement?: boolean;
25
33
  }
26
34
  ];
27
35
 
@@ -34,8 +42,9 @@ declare const plugin: {
34
42
  "consistent-list-newline": RuleModule<Options>;
35
43
  "if-newline": RuleModule<[]>;
36
44
  "import-dedupe": RuleModule<[]>;
37
- "no-import-node-modules-by-path": RuleModule<[]>;
45
+ "indent-unindent": RuleModule<Options$1>;
38
46
  "no-import-dist": RuleModule<[]>;
47
+ "no-import-node-modules-by-path": RuleModule<[]>;
39
48
  "no-ts-export-equal": RuleModule<[]>;
40
49
  "top-level-function": RuleModule<[]>;
41
50
  };
package/dist/index.d.ts CHANGED
@@ -4,6 +4,13 @@ interface RuleModule<T extends readonly unknown[]> extends Rule.RuleModule {
4
4
  defaultOptions: T;
5
5
  }
6
6
 
7
+ type Options$1 = [
8
+ {
9
+ indent?: number;
10
+ tags?: string[];
11
+ }
12
+ ];
13
+
7
14
  type Options = [
8
15
  {
9
16
  ArrayExpression?: boolean;
@@ -22,6 +29,7 @@ type Options = [
22
29
  TSTypeParameterInstantiation?: boolean;
23
30
  ObjectPattern?: boolean;
24
31
  ArrayPattern?: boolean;
32
+ JSXOpeningElement?: boolean;
25
33
  }
26
34
  ];
27
35
 
@@ -34,8 +42,9 @@ declare const plugin: {
34
42
  "consistent-list-newline": RuleModule<Options>;
35
43
  "if-newline": RuleModule<[]>;
36
44
  "import-dedupe": RuleModule<[]>;
37
- "no-import-node-modules-by-path": RuleModule<[]>;
45
+ "indent-unindent": RuleModule<Options$1>;
38
46
  "no-import-dist": RuleModule<[]>;
47
+ "no-import-node-modules-by-path": RuleModule<[]>;
39
48
  "no-ts-export-equal": RuleModule<[]>;
40
49
  "top-level-function": RuleModule<[]>;
41
50
  };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- const version = "1.1.0";
1
+ const version = "1.3.0";
2
2
 
3
3
  const hasDocs = [
4
4
  "consistent-list-newline",
@@ -47,6 +47,27 @@ function createRule({
47
47
  const createEslintRule = RuleCreator(
48
48
  (ruleName) => hasDocs.includes(ruleName) ? `${blobUrl}${ruleName}.md` : `${blobUrl}${ruleName}.test.ts`
49
49
  );
50
+ const _reFullWs = /^\s*$/;
51
+ function unindent(str) {
52
+ const lines = (typeof str === "string" ? str : str[0]).split("\n");
53
+ const whitespaceLines = lines.map((line) => _reFullWs.test(line));
54
+ const commonIndent = lines.reduce((min, line, idx) => {
55
+ if (whitespaceLines[idx]) {
56
+ return min;
57
+ }
58
+ const indent = line.match(/^\s*/)?.[0].length;
59
+ return indent === void 0 ? min : Math.min(min, indent);
60
+ }, Number.POSITIVE_INFINITY);
61
+ let emptyLinesHead = 0;
62
+ while (emptyLinesHead < lines.length && whitespaceLines[emptyLinesHead]) {
63
+ emptyLinesHead++;
64
+ }
65
+ let emptyLinesTail = 0;
66
+ while (emptyLinesTail < lines.length && whitespaceLines[lines.length - emptyLinesTail - 1]) {
67
+ emptyLinesTail++;
68
+ }
69
+ return lines.slice(emptyLinesHead, lines.length - emptyLinesTail).map((line) => line.slice(commonIndent)).join("\n");
70
+ }
50
71
 
51
72
  const RULE_NAME$6 = "if-newline";
52
73
  const ifNewline = createEslintRule({
@@ -349,7 +370,8 @@ const consistentListNewline = createEslintRule({
349
370
  TSTypeParameterDeclaration: { type: "boolean" },
350
371
  TSTypeParameterInstantiation: { type: "boolean" },
351
372
  ObjectPattern: { type: "boolean" },
352
- ArrayPattern: { type: "boolean" }
373
+ ArrayPattern: { type: "boolean" },
374
+ JSXOpeningElement: { type: "boolean" }
353
375
  },
354
376
  additionalProperties: false
355
377
  }],
@@ -370,7 +392,10 @@ const consistentListNewline = createEslintRule({
370
392
  if (items.length === 0) {
371
393
  return;
372
394
  }
373
- const startToken = context.sourceCode.getTokenBefore(items[0]);
395
+ let startToken = ["CallExpression", "NewExpression"].includes(node.type) ? void 0 : context.sourceCode.getFirstToken(node);
396
+ if (startToken?.type !== "Punctuator") {
397
+ startToken = context.sourceCode.getTokenBefore(items[0]);
398
+ }
374
399
  const endToken = context.sourceCode.getTokenAfter(items[items.length - 1]);
375
400
  const startLine = startToken.loc.start.line;
376
401
  if (startToken.loc.start.line === endToken.loc.end.line) {
@@ -398,16 +423,22 @@ const consistentListNewline = createEslintRule({
398
423
  });
399
424
  } else if (mode === "inline" && currentStart !== lastLine) {
400
425
  const lastItem2 = items[idx - 1];
401
- context.report({
402
- node: item,
403
- messageId: "shouldNotWrap",
404
- data: {
405
- name: node.type
406
- },
407
- *fix(fixer) {
408
- yield removeLines(fixer, lastItem2.range[1], item.range[0]);
409
- }
410
- });
426
+ if (context.sourceCode.getCommentsBefore(item).length > 0) {
427
+ return;
428
+ }
429
+ const content = context.sourceCode.text.slice(lastItem2.range[1], item.range[0]);
430
+ if (content.includes("\n")) {
431
+ context.report({
432
+ node: item,
433
+ messageId: "shouldNotWrap",
434
+ data: {
435
+ name: node.type
436
+ },
437
+ *fix(fixer) {
438
+ yield removeLines(fixer, lastItem2.range[1], item.range[0]);
439
+ }
440
+ });
441
+ }
411
442
  }
412
443
  lastLine = item.loc.end.line;
413
444
  });
@@ -432,6 +463,9 @@ const consistentListNewline = createEslintRule({
432
463
  if (items.length === 1 && items[0].loc.start.line !== items[1]?.loc.start.line) {
433
464
  return;
434
465
  }
466
+ if (context.sourceCode.getCommentsAfter(lastItem).length > 0) {
467
+ return;
468
+ }
435
469
  const content = context.sourceCode.text.slice(lastItem.range[1], endRange);
436
470
  if (content.includes("\n")) {
437
471
  context.report({
@@ -510,6 +544,12 @@ const consistentListNewline = createEslintRule({
510
544
  },
511
545
  ArrayPattern(node) {
512
546
  check(node, node.elements);
547
+ },
548
+ JSXOpeningElement(node) {
549
+ if (node.attributes.some((attr) => attr.loc.start.line !== attr.loc.end.line)) {
550
+ return;
551
+ }
552
+ check(node, node.attributes);
513
553
  }
514
554
  };
515
555
  Object.keys(options).forEach((key) => {
@@ -521,17 +561,93 @@ const consistentListNewline = createEslintRule({
521
561
  }
522
562
  });
523
563
 
564
+ const indentUnindent = createEslintRule({
565
+ name: "indent-unindent",
566
+ meta: {
567
+ type: "layout",
568
+ docs: {
569
+ description: "Enforce consistent indentation in `unindent` template tag"
570
+ },
571
+ fixable: "code",
572
+ schema: [
573
+ {
574
+ type: "object",
575
+ properties: {
576
+ indent: {
577
+ type: "number",
578
+ minimum: 0,
579
+ default: 2
580
+ },
581
+ tags: {
582
+ type: "array",
583
+ items: {
584
+ type: "string"
585
+ }
586
+ }
587
+ },
588
+ additionalProperties: false
589
+ }
590
+ ],
591
+ messages: {
592
+ "indent-unindent": "Consistent indentation in unindent tag"
593
+ }
594
+ },
595
+ defaultOptions: [{}],
596
+ create(context) {
597
+ const {
598
+ tags = ["$", "unindent", "unIndent"],
599
+ indent = 2
600
+ } = context.options?.[0] ?? {};
601
+ return {
602
+ TaggedTemplateExpression(node) {
603
+ const id = node.tag;
604
+ if (!id || id.type !== "Identifier") {
605
+ return;
606
+ }
607
+ if (!tags.includes(id.name)) {
608
+ return;
609
+ }
610
+ if (node.quasi.quasis.length !== 1) {
611
+ return;
612
+ }
613
+ const quasi = node.quasi.quasis[0];
614
+ const value = quasi.value.raw;
615
+ const lineStartIndex = context.sourceCode.getIndexFromLoc({
616
+ line: node.loc.start.line,
617
+ column: 0
618
+ });
619
+ const baseIndent = context.sourceCode.text.slice(lineStartIndex).match(/^\s*/)?.[0] ?? "";
620
+ const targetIndent = baseIndent + " ".repeat(indent);
621
+ const pure = unindent([value]);
622
+ let final = pure.split("\n").map((line) => targetIndent + line).join("\n");
623
+ final = `
624
+ ${final}
625
+ ${baseIndent}`;
626
+ if (final !== value) {
627
+ context.report({
628
+ node: quasi,
629
+ messageId: "indent-unindent",
630
+ fix: (fixer) => fixer.replaceText(quasi, `\`${final}\``)
631
+ });
632
+ }
633
+ }
634
+ };
635
+ }
636
+ });
637
+
524
638
  const plugin = {
525
639
  meta: {
526
640
  name: "kirklin",
527
641
  version
528
642
  },
643
+ // @keep-sorted
529
644
  rules: {
530
645
  "consistent-list-newline": consistentListNewline,
531
646
  "if-newline": ifNewline,
532
647
  "import-dedupe": importDedupe,
533
- "no-import-node-modules-by-path": noImportNodeModulesByPath,
648
+ "indent-unindent": indentUnindent,
534
649
  "no-import-dist": noImportDist,
650
+ "no-import-node-modules-by-path": noImportNodeModulesByPath,
535
651
  "no-ts-export-equal": noTsExportEqual,
536
652
  "top-level-function": topLevelFunction
537
653
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "eslint-plugin-kirklin",
3
3
  "type": "module",
4
- "version": "1.1.0",
5
- "packageManager": "pnpm@8.10.2",
4
+ "version": "1.3.0",
5
+ "packageManager": "pnpm@9.1.0",
6
6
  "description": "Kirk Lin extended ESLint rules",
7
7
  "author": "Kirk Lin (https://github.com/kirklin)",
8
8
  "license": "MIT",
@@ -41,34 +41,34 @@
41
41
  "prepare": "simple-git-hooks"
42
42
  },
43
43
  "peerDependencies": {
44
- "eslint": "*"
44
+ "eslint": ">=8.40.0"
45
45
  },
46
46
  "devDependencies": {
47
- "@antfu/ni": "^0.21.8",
48
- "@kirklin/eslint-config": "^1.1.1",
49
- "@types/eslint": "^8.44.6",
50
- "@types/lodash.merge": "^4.6.8",
51
- "@types/node": "^20.8.10",
52
- "@types/semver": "^7.5.4",
53
- "@typescript-eslint/rule-tester": "^6.9.1",
54
- "@typescript-eslint/typescript-estree": "^6.9.1",
55
- "@typescript-eslint/utils": "^6.9.1",
56
- "ajv": "^8.12.0",
57
- "bumpp": "^9.2.0",
58
- "eslint": "^8.53.0",
59
- "eslint-define-config": "^1.24.1",
60
- "esno": "^0.17.0",
61
- "lint-staged": "^15.0.2",
47
+ "@antfu/ni": "^0.21.12",
48
+ "@kirklin/eslint-config": "^2.3.0",
49
+ "@types/eslint": "^8.56.10",
50
+ "@types/lodash.merge": "^4.6.9",
51
+ "@types/node": "^20.12.11",
52
+ "@types/semver": "^7.5.8",
53
+ "@typescript-eslint/typescript-estree": "^7.8.0",
54
+ "@typescript-eslint/utils": "^7.8.0",
55
+ "bumpp": "^9.4.1",
56
+ "eslint": "^9.2.0",
57
+ "eslint-define-config": "^2.1.0",
58
+ "eslint-vitest-rule-tester": "^0.3.2",
59
+ "esno": "^4.7.0",
60
+ "lint-staged": "^15.2.2",
62
61
  "lodash.merge": "4.6.2",
63
- "pnpm": "^8.10.2",
62
+ "pnpm": "^9.1.0",
64
63
  "rimraf": "^5.0.5",
65
- "semver": "^7.5.4",
66
- "simple-git-hooks": "^2.9.0",
67
- "tsup": "^7.2.0",
68
- "typescript": "^5.2.2",
64
+ "semver": "^7.6.1",
65
+ "simple-git-hooks": "^2.11.1",
66
+ "tsup": "^8.0.2",
67
+ "tsx": "^4.9.3",
68
+ "typescript": "^5.4.5",
69
69
  "unbuild": "^2.0.0",
70
- "vite": "^4.5.0",
71
- "vitest": "^0.34.6"
70
+ "vite": "^5.2.11",
71
+ "vitest": "^1.6.0"
72
72
  },
73
73
  "resolutions": {
74
74
  "eslint-plugin-kirklin": "workspace:*"
@@ -79,4 +79,4 @@
79
79
  "lint-staged": {
80
80
  "*": "eslint --fix"
81
81
  }
82
- }
82
+ }