@flint.fyi/plugin-flint 0.8.0 → 0.8.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.mts CHANGED
@@ -1,95 +1,55 @@
1
1
  //#region src/plugin.d.ts
2
- declare const flint: import("@flint.fyi/core").Plugin<import("@flint.fyi/core").RuleAbout<string>, string | undefined, [import("@flint.fyi/core").Rule<{
2
+ export declare const flint: import("@flint.fyi/core").Plugin<import("@flint.fyi/core").RuleAbout<string>, string | undefined, [import("@flint.fyi/core").Rule<import("@flint.fyi/core").PluginRuleAbout<string> & {
3
3
  readonly description: "Requires passing `sourceFile` to `getStart()` for better performance.";
4
4
  readonly id: "getStartSourceFile";
5
5
  readonly presets: readonly ["logical"];
6
- } & {
7
- readonly pluginId: string;
8
- readonly url: string;
9
- }, "missingSourceFile", undefined>, import("@flint.fyi/core").Rule<{
6
+ }, "missingSourceFile", undefined>, import("@flint.fyi/core").Rule<import("@flint.fyi/core").PluginRuleAbout<string> & {
10
7
  readonly description: "Reports cases for invalid code that isn't formatted across lines.";
11
8
  readonly id: "invalidCodeLines";
12
9
  readonly presets: readonly ["logical"];
13
- } & {
14
- readonly pluginId: string;
15
- readonly url: string;
16
- }, "singleLineTest", undefined>, import("@flint.fyi/core").Rule<{
10
+ }, "singleLineTest", undefined>, import("@flint.fyi/core").Rule<import("@flint.fyi/core").PluginRuleAbout<string> & {
17
11
  readonly description: "Reports `context.report()` calls missing data for message placeholders.";
18
12
  readonly id: "missingPlaceholders";
19
13
  readonly presets: readonly ["logical"];
20
- } & {
21
- readonly pluginId: string;
22
- readonly url: string;
23
- }, "missingPlaceholders", undefined>, import("@flint.fyi/core").Rule<{
14
+ }, "missingPlaceholders", undefined>, import("@flint.fyi/core").Rule<import("@flint.fyi/core").PluginRuleAbout<string> & {
24
15
  readonly description: "Disallows using the `in` operator to check properties on TypeScript nodes.";
25
16
  readonly id: "nodePropertyInChecks";
26
17
  readonly presets: readonly ["logical"];
27
- } & {
28
- readonly pluginId: string;
29
- readonly url: string;
30
- }, "nodePropertyInChecks", undefined>, import("@flint.fyi/core").Rule<{
18
+ }, "nodePropertyInChecks", undefined>, import("@flint.fyi/core").Rule<import("@flint.fyi/core").PluginRuleAbout<string> & {
31
19
  readonly description: "Reports and auto-fixes message placeholders that are not formatted as `{{ placeholder }}`.";
32
20
  readonly id: "placeholderFormats";
33
21
  readonly presets: readonly ["stylistic", "stylisticStrict"];
34
- } & {
35
- readonly pluginId: string;
36
- readonly url: string;
37
- }, "placeholderFormats", undefined>, import("@flint.fyi/core").Rule<{
22
+ }, "placeholderFormats", undefined>, import("@flint.fyi/core").Rule<import("@flint.fyi/core").PluginRuleAbout<string> & {
38
23
  readonly description: "Reports Flint plugin `rules` arrays that are not sorted alphabetically.";
39
24
  readonly id: "pluginRuleOrdering";
40
25
  readonly presets: readonly ["stylisticStrict"];
41
- } & {
42
- readonly pluginId: string;
43
- readonly url: string;
44
- }, "pluginRuleOrdering", undefined>, import("@flint.fyi/core").Rule<{
26
+ }, "pluginRuleOrdering", undefined>, import("@flint.fyi/core").Rule<import("@flint.fyi/core").PluginRuleAbout<string> & {
45
27
  readonly description: "Reports plugin rules created directly with language instead of through RuleCreator.";
46
28
  readonly id: "ruleCreationMethods";
47
29
  readonly presets: readonly ["logical"];
48
- } & {
49
- readonly pluginId: string;
50
- readonly url: string;
51
- }, "ruleCreationMethods", undefined>, import("@flint.fyi/core").Rule<{
30
+ }, "ruleCreationMethods", undefined>, import("@flint.fyi/core").Rule<import("@flint.fyi/core").PluginRuleAbout<string> & {
52
31
  readonly description: "Reports test cases that are identical to previous test cases.";
53
32
  readonly id: "testCaseDuplicates";
54
33
  readonly presets: readonly ["logical"];
55
- } & {
56
- readonly pluginId: string;
57
- readonly url: string;
58
- }, "duplicateTest", undefined>, import("@flint.fyi/core").Rule<{
34
+ }, "duplicateTest", undefined>, import("@flint.fyi/core").Rule<import("@flint.fyi/core").PluginRuleAbout<string> & {
59
35
  readonly description: "Reports test cases that have the same name as a previous test case.";
60
36
  readonly id: "testCaseNameDuplicates";
61
37
  readonly presets: readonly ["logical"];
62
- } & {
63
- readonly pluginId: string;
64
- readonly url: string;
65
- }, "duplicateTestName", undefined>, import("@flint.fyi/core").Rule<{
38
+ }, "duplicateTestName", undefined>, import("@flint.fyi/core").Rule<import("@flint.fyi/core").PluginRuleAbout<string> & {
66
39
  readonly description: "Test case code should be a static string literal.";
67
40
  readonly id: "testCaseNonStaticCode";
68
41
  readonly presets: readonly ["logical"];
69
- } & {
70
- readonly pluginId: string;
71
- readonly url: string;
72
- }, "nonStaticTestCaseCode", undefined>, import("@flint.fyi/core").Rule<{
42
+ }, "nonStaticTestCaseCode", undefined>, import("@flint.fyi/core").Rule<import("@flint.fyi/core").PluginRuleAbout<string> & {
73
43
  readonly description: "Reports test cases that are marked `only: true`.";
74
44
  readonly id: "testCaseOnlyFlags";
75
45
  readonly presets: readonly ["logical"];
76
- } & {
77
- readonly pluginId: string;
78
- readonly url: string;
79
- }, "testCaseOnly", undefined>, import("@flint.fyi/core").Rule<{
46
+ }, "testCaseOnly", undefined>, import("@flint.fyi/core").Rule<import("@flint.fyi/core").PluginRuleAbout<string> & {
80
47
  readonly description: "Test cases with only a code property can use string shorthand syntax instead of object literal syntax.";
81
48
  readonly id: "testShorthands";
82
49
  readonly presets: readonly ["logical"];
83
- } & {
84
- readonly pluginId: string;
85
- readonly url: string;
86
- }, "testShorthands", undefined>, import("@flint.fyi/core").Rule<{
50
+ }, "testShorthands", undefined>, import("@flint.fyi/core").Rule<import("@flint.fyi/core").PluginRuleAbout<string> & {
87
51
  readonly description: "Reports message IDs defined in the messages object that are never used in recognized report calls.";
88
52
  readonly id: "unusedMessageIds";
89
53
  readonly presets: readonly ["logical"];
90
- } & {
91
- readonly pluginId: string;
92
- readonly url: string;
93
54
  }, "unusedMessageIds", undefined>]>;
94
- //#endregion
95
- export { flint };
55
+ //#endregion
package/dist/index.mjs CHANGED
@@ -87,10 +87,10 @@ function tsAstToLiteral(node) {
87
87
  case SyntaxKind.NullKeyword: return null;
88
88
  case SyntaxKind.TrueKeyword: return true;
89
89
  }
90
- if (ts.isArrayLiteralExpression(node)) return node.elements.filter((element) => element.kind !== SyntaxKind.SpreadElement).map((element) => tsAstToLiteral(element));
91
- if (ts.isNumericLiteral(node)) return parseFloat(node.text);
92
- if (ts.isObjectLiteralExpression(node)) return Object.fromEntries(node.properties.filter((property) => ts.isPropertyAssignment(property) && (property.name.kind === SyntaxKind.Identifier || property.name.kind === SyntaxKind.StringLiteral)).map((property) => [property.name.escapedText || property.name.text, tsAstToLiteral(property.initializer)]));
93
- if (ts.isStringLiteral(node)) return node.text;
90
+ if (node.kind === SyntaxKind.ArrayLiteralExpression) return node.elements.filter((element) => element.kind !== SyntaxKind.SpreadElement).map((element) => tsAstToLiteral(element));
91
+ if (node.kind === SyntaxKind.NumericLiteral) return parseFloat(node.text);
92
+ if (node.kind === SyntaxKind.ObjectLiteralExpression) return Object.fromEntries(node.properties.filter((property) => property.kind === SyntaxKind.PropertyAssignment && (property.name.kind === SyntaxKind.Identifier || property.name.kind === SyntaxKind.StringLiteral)).map((property) => [property.name.text, tsAstToLiteral(property.initializer)]));
93
+ if (node.kind === SyntaxKind.StringLiteral) return node.text;
94
94
  }
95
95
  //#endregion
96
96
  //#region src/utils/parseTestCases.ts
@@ -133,7 +133,7 @@ function parseTestCaseInvalid(node) {
133
133
  const files = findProperty(node.properties, "files", (node) => node.kind === SyntaxKind.ObjectLiteralExpression);
134
134
  const name = findProperty(node.properties, "name", isStaticString);
135
135
  const options = findProperty(node.properties, "options", (node) => node.kind === SyntaxKind.ObjectLiteralExpression);
136
- const snapshot = findProperty(node.properties, "snapshot", isStaticString);
136
+ const snapshot = findProperty(node.properties, "snapshot", isTestCaseCode);
137
137
  if (!snapshot) return;
138
138
  return {
139
139
  code: getTestCaseCode(code),
@@ -150,7 +150,7 @@ function parseTestCaseInvalid(node) {
150
150
  snapshot
151
151
  },
152
152
  options: options && tsAstToLiteral(options),
153
- snapshot: snapshot.text
153
+ snapshot: getTestCaseCode(snapshot)
154
154
  };
155
155
  }
156
156
  function getTestCaseCode(node) {
@@ -363,7 +363,7 @@ var nodePropertyInChecks_default = ruleCreator.createRule(typescriptLanguage, {
363
363
  messages: { nodePropertyInChecks: {
364
364
  primary: "Avoid using the `in` operator to check properties on TypeScript nodes.",
365
365
  secondary: ["The `in` operator checks inherited properties and may not work reliably with TypeScript AST nodes.", "TypeScript AST nodes have complex prototype chains that can lead to unexpected results with `in` checks."],
366
- suggestions: ["Access the property directly or use a type guard function like `ts.isXXX()` instead."]
366
+ suggestions: ["Access the property directly or compare `node.kind` to the matching `SyntaxKind` instead."]
367
367
  } },
368
368
  setup(context) {
369
369
  return { visitors: { BinaryExpression(node, { sourceFile, typeChecker }) {
@@ -755,6 +755,71 @@ function isVolarReportSourceCodeCall(node, typeChecker) {
755
755
  if (node.expression.kind === SyntaxKind.PropertyAccessExpression && node.expression.expression.kind === SyntaxKind.Identifier && node.expression.name.text === "reportSourceCode") return typeChecker.getSymbolAtLocation(node.expression.expression)?.getDeclarations()?.some((declaration) => declaration.kind === SyntaxKind.NamespaceImport && isImportedBindingFromModule(declaration, volarLanguagePackageName)) ?? false;
756
756
  return false;
757
757
  }
758
+ var unusedMessageIds_default = ruleCreator.createRule(typescriptLanguage, {
759
+ about: {
760
+ description: "Reports message IDs defined in the messages object that are never used in recognized report calls.",
761
+ id: "unusedMessageIds",
762
+ presets: ["logical"]
763
+ },
764
+ messages: { unusedMessageIds: {
765
+ primary: "Message ID '{{ messageId }}' is defined but never used.",
766
+ secondary: ["This message ID is declared in the messages object but is not referenced in any `context.report()` or built-in Flint report helper call.", "Remove unused message IDs to keep the rule configuration clean and maintainable."],
767
+ suggestions: ["Remove the unused message ID from the messages object."]
768
+ } },
769
+ setup(context) {
770
+ const unusedMessageIds = /* @__PURE__ */ new Map();
771
+ function collectMessageIds(node, sourceFile) {
772
+ const args = node.arguments[1];
773
+ if (args?.kind !== SyntaxKind.ObjectLiteralExpression) return;
774
+ const messagesProperty = findProperty(args.properties, "messages", (node) => node.kind === SyntaxKind.ObjectLiteralExpression);
775
+ if (!messagesProperty) return;
776
+ for (const prop of messagesProperty.properties) {
777
+ if (prop.kind !== SyntaxKind.PropertyAssignment || prop.name.kind !== SyntaxKind.Identifier) continue;
778
+ const messageId = prop.name.text;
779
+ if (!unusedMessageIds.has(messageId)) unusedMessageIds.set(messageId, getTSNodeRange(prop.name, sourceFile));
780
+ }
781
+ }
782
+ function detectMessageIdUsage(node, reportArgumentIndex) {
783
+ if (!unusedMessageIds.size) return;
784
+ const args = node.arguments[reportArgumentIndex];
785
+ if (args?.kind !== SyntaxKind.ObjectLiteralExpression) {
786
+ unusedMessageIds.clear();
787
+ return;
788
+ }
789
+ const messageProperty = findProperty(args.properties, "message", (node) => node.kind === SyntaxKind.StringLiteral);
790
+ if (!messageProperty) {
791
+ unusedMessageIds.clear();
792
+ return;
793
+ }
794
+ unusedMessageIds.delete(messageProperty.text);
795
+ }
796
+ return { visitors: {
797
+ CallExpression(node, { sourceFile, typeChecker }) {
798
+ if (isRuleCreatorCreateRule(node, typeChecker)) {
799
+ collectMessageIds(node, sourceFile);
800
+ return;
801
+ }
802
+ if (isRuleContextReport(node, typeChecker)) {
803
+ detectMessageIdUsage(node, 0);
804
+ return;
805
+ }
806
+ if (isVolarReportSourceCodeCall(node, typeChecker)) {
807
+ detectMessageIdUsage(node, 1);
808
+ return;
809
+ }
810
+ },
811
+ "SourceFile:exit"() {
812
+ if (!unusedMessageIds.size) return;
813
+ for (const [messageId, range] of unusedMessageIds) context.report({
814
+ data: { messageId },
815
+ message: "unusedMessageIds",
816
+ range
817
+ });
818
+ unusedMessageIds.clear();
819
+ }
820
+ } };
821
+ }
822
+ });
758
823
  //#endregion
759
824
  //#region src/plugin.ts
760
825
  const flint = createPlugin({
@@ -772,71 +837,7 @@ const flint = createPlugin({
772
837
  testCaseNonStaticCode_default,
773
838
  testCaseOnlyFlags_default,
774
839
  testShorthands_default,
775
- ruleCreator.createRule(typescriptLanguage, {
776
- about: {
777
- description: "Reports message IDs defined in the messages object that are never used in recognized report calls.",
778
- id: "unusedMessageIds",
779
- presets: ["logical"]
780
- },
781
- messages: { unusedMessageIds: {
782
- primary: "Message ID '{{ messageId }}' is defined but never used.",
783
- secondary: ["This message ID is declared in the messages object but is not referenced in any `context.report()` or built-in Flint report helper call.", "Remove unused message IDs to keep the rule configuration clean and maintainable."],
784
- suggestions: ["Remove the unused message ID from the messages object."]
785
- } },
786
- setup(context) {
787
- const unusedMessageIds = /* @__PURE__ */ new Map();
788
- function collectMessageIds(node, sourceFile) {
789
- const args = node.arguments[1];
790
- if (args?.kind !== SyntaxKind.ObjectLiteralExpression) return;
791
- const messagesProperty = findProperty(args.properties, "messages", (node) => node.kind === SyntaxKind.ObjectLiteralExpression);
792
- if (!messagesProperty) return;
793
- for (const prop of messagesProperty.properties) {
794
- if (prop.kind !== SyntaxKind.PropertyAssignment || prop.name.kind !== SyntaxKind.Identifier) continue;
795
- const messageId = prop.name.text;
796
- if (!unusedMessageIds.has(messageId)) unusedMessageIds.set(messageId, getTSNodeRange(prop.name, sourceFile));
797
- }
798
- }
799
- function detectMessageIdUsage(node, reportArgumentIndex) {
800
- if (!unusedMessageIds.size) return;
801
- const args = node.arguments[reportArgumentIndex];
802
- if (args?.kind !== SyntaxKind.ObjectLiteralExpression) {
803
- unusedMessageIds.clear();
804
- return;
805
- }
806
- const messageProperty = findProperty(args.properties, "message", (node) => node.kind === SyntaxKind.StringLiteral);
807
- if (!messageProperty) {
808
- unusedMessageIds.clear();
809
- return;
810
- }
811
- unusedMessageIds.delete(messageProperty.text);
812
- }
813
- return { visitors: {
814
- CallExpression(node, { sourceFile, typeChecker }) {
815
- if (isRuleCreatorCreateRule(node, typeChecker)) {
816
- collectMessageIds(node, sourceFile);
817
- return;
818
- }
819
- if (isRuleContextReport(node, typeChecker)) {
820
- detectMessageIdUsage(node, 0);
821
- return;
822
- }
823
- if (isVolarReportSourceCodeCall(node, typeChecker)) {
824
- detectMessageIdUsage(node, 1);
825
- return;
826
- }
827
- },
828
- "SourceFile:exit"() {
829
- if (!unusedMessageIds.size) return;
830
- for (const [messageId, range] of unusedMessageIds) context.report({
831
- data: { messageId },
832
- message: "unusedMessageIds",
833
- range
834
- });
835
- unusedMessageIds.clear();
836
- }
837
- } };
838
- }
839
- })
840
+ unusedMessageIds_default
840
841
  ]
841
842
  });
842
843
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flint.fyi/plugin-flint",
3
- "version": "0.8.0",
3
+ "version": "0.8.2",
4
4
  "description": "[Experimental] A Flint plugin for linting Flint plugins.",
5
5
  "homepage": "https://flint.fyi/rules/flint",
6
6
  "repository": {
@@ -22,16 +22,17 @@
22
22
  "dist/"
23
23
  ],
24
24
  "dependencies": {
25
- "typescript": "^6.0.0",
26
- "@flint.fyi/typescript-language": "^0.20.0",
25
+ "@flint.fyi/core": "^0.27.0",
26
+ "@flint.fyi/typescript-language": "^0.21.0",
27
27
  "@flint.fyi/utils": "^0.16.0",
28
- "@flint.fyi/core": "^0.25.0"
28
+ "typescript": "^6.0.3"
29
29
  },
30
30
  "devDependencies": {
31
- "tsdown": "0.22.3",
32
- "vitest": "4.1.0",
33
- "@flint.fyi/rule-tester": "^0.18.0",
34
- "@flint.fyi/build": "^0.1.0"
31
+ "@flint.fyi/build": "^0.1.0",
32
+ "@flint.fyi/rule-tester": "^0.20.0",
33
+ "@flint.fyi/volar-language": "^0.4.0",
34
+ "tsdown": "0.23.0",
35
+ "vitest": "5.0.1"
35
36
  },
36
37
  "engines": {
37
38
  "node": ">=26.1.0"