eslint-plugin-kirklin 1.2.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.2.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
  }],
@@ -406,16 +428,19 @@ const consistentListNewline = createEslintRule({
406
428
  if (context.sourceCode.getCommentsBefore(item).length > 0) {
407
429
  return;
408
430
  }
409
- context.report({
410
- node: item,
411
- messageId: "shouldNotWrap",
412
- data: {
413
- name: node.type
414
- },
415
- *fix(fixer) {
416
- yield removeLines(fixer, lastItem2.range[1], item.range[0]);
417
- }
418
- });
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
+ }
419
444
  }
420
445
  lastLine = item.loc.end.line;
421
446
  });
@@ -521,30 +546,110 @@ const consistentListNewline = createEslintRule({
521
546
  },
522
547
  ArrayPattern(node) {
523
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);
524
555
  }
525
556
  };
526
- Object.keys(options).forEach(
527
- (key) => {
528
- if (options[key] === false) {
529
- delete listenser[key];
530
- }
557
+ Object.keys(options).forEach((key) => {
558
+ if (options[key] === false) {
559
+ delete listenser[key];
531
560
  }
532
- );
561
+ });
533
562
  return listenser;
534
563
  }
535
564
  });
536
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
+
537
640
  const plugin = {
538
641
  meta: {
539
642
  name: "kirklin",
540
643
  version
541
644
  },
645
+ // @keep-sorted
542
646
  rules: {
543
647
  "consistent-list-newline": consistentListNewline,
544
648
  "if-newline": ifNewline,
545
649
  "import-dedupe": importDedupe,
546
- "no-import-node-modules-by-path": noImportNodeModulesByPath,
650
+ "indent-unindent": indentUnindent,
547
651
  "no-import-dist": noImportDist,
652
+ "no-import-node-modules-by-path": noImportNodeModulesByPath,
548
653
  "no-ts-export-equal": noTsExportEqual,
549
654
  "top-level-function": topLevelFunction
550
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.2.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
  }],
@@ -404,16 +426,19 @@ const consistentListNewline = createEslintRule({
404
426
  if (context.sourceCode.getCommentsBefore(item).length > 0) {
405
427
  return;
406
428
  }
407
- context.report({
408
- node: item,
409
- messageId: "shouldNotWrap",
410
- data: {
411
- name: node.type
412
- },
413
- *fix(fixer) {
414
- yield removeLines(fixer, lastItem2.range[1], item.range[0]);
415
- }
416
- });
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
+ }
417
442
  }
418
443
  lastLine = item.loc.end.line;
419
444
  });
@@ -519,30 +544,110 @@ const consistentListNewline = createEslintRule({
519
544
  },
520
545
  ArrayPattern(node) {
521
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);
522
553
  }
523
554
  };
524
- Object.keys(options).forEach(
525
- (key) => {
526
- if (options[key] === false) {
527
- delete listenser[key];
528
- }
555
+ Object.keys(options).forEach((key) => {
556
+ if (options[key] === false) {
557
+ delete listenser[key];
529
558
  }
530
- );
559
+ });
531
560
  return listenser;
532
561
  }
533
562
  });
534
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
+
535
638
  const plugin = {
536
639
  meta: {
537
640
  name: "kirklin",
538
641
  version
539
642
  },
643
+ // @keep-sorted
540
644
  rules: {
541
645
  "consistent-list-newline": consistentListNewline,
542
646
  "if-newline": ifNewline,
543
647
  "import-dedupe": importDedupe,
544
- "no-import-node-modules-by-path": noImportNodeModulesByPath,
648
+ "indent-unindent": indentUnindent,
545
649
  "no-import-dist": noImportDist,
650
+ "no-import-node-modules-by-path": noImportNodeModulesByPath,
546
651
  "no-ts-export-equal": noTsExportEqual,
547
652
  "top-level-function": topLevelFunction
548
653
  }
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "eslint-plugin-kirklin",
3
3
  "type": "module",
4
- "version": "1.2.0",
5
- "packageManager": "pnpm@9.0.1",
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",
@@ -46,29 +46,29 @@
46
46
  "devDependencies": {
47
47
  "@antfu/ni": "^0.21.12",
48
48
  "@kirklin/eslint-config": "^2.3.0",
49
- "@types/eslint": "^8.56.9",
49
+ "@types/eslint": "^8.56.10",
50
50
  "@types/lodash.merge": "^4.6.9",
51
- "@types/node": "^20.12.7",
51
+ "@types/node": "^20.12.11",
52
52
  "@types/semver": "^7.5.8",
53
- "@typescript-eslint/rule-tester": "^7.7.0",
54
- "@typescript-eslint/typescript-estree": "^7.7.0",
55
- "@typescript-eslint/utils": "^7.7.0",
56
- "ajv": "^6.12.4",
57
- "bumpp": "^9.4.0",
58
- "eslint": "^9.0.0",
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",
59
57
  "eslint-define-config": "^2.1.0",
58
+ "eslint-vitest-rule-tester": "^0.3.2",
60
59
  "esno": "^4.7.0",
61
60
  "lint-staged": "^15.2.2",
62
61
  "lodash.merge": "4.6.2",
63
- "pnpm": "^9.0.1",
62
+ "pnpm": "^9.1.0",
64
63
  "rimraf": "^5.0.5",
65
- "semver": "^7.6.0",
64
+ "semver": "^7.6.1",
66
65
  "simple-git-hooks": "^2.11.1",
67
66
  "tsup": "^8.0.2",
67
+ "tsx": "^4.9.3",
68
68
  "typescript": "^5.4.5",
69
69
  "unbuild": "^2.0.0",
70
- "vite": "^5.2.9",
71
- "vitest": "^1.5.0"
70
+ "vite": "^5.2.11",
71
+ "vitest": "^1.6.0"
72
72
  },
73
73
  "resolutions": {
74
74
  "eslint-plugin-kirklin": "workspace:*"