@saasmakers/eslint 1.0.21 → 1.0.23

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.
@@ -2,6 +2,7 @@
2
2
 
3
3
  const antfu = require('@antfu/eslint-config');
4
4
  const saasmakers = require('@saasmakers/eslint');
5
+ const shared = require('@saasmakers/shared');
5
6
  const vitest = require('@vitest/eslint-plugin');
6
7
  const packageJson = require('eslint-plugin-package-json');
7
8
  const index$1 = require('./shared/eslint.BqRQ4tAN.cjs');
@@ -10869,7 +10870,7 @@ const eslint_config = antfu__default(
10869
10870
  rules: {
10870
10871
  "saasmakers/ts-format-layout": "error",
10871
10872
  "saasmakers/ts-format-tests": "error",
10872
- "saasmakers/vue-format-i18n": "error",
10873
+ "saasmakers/vue-format-i18n": ["error", { locales: shared.localeCodes }],
10873
10874
  "saasmakers/vue-format-script": "error",
10874
10875
  "saasmakers/vue-format-template": "error"
10875
10876
  }
@@ -1,5 +1,6 @@
1
1
  import antfu from '@antfu/eslint-config';
2
2
  import saasmakers from '@saasmakers/eslint';
3
+ import { localeCodes } from '@saasmakers/shared';
3
4
  import vitest from '@vitest/eslint-plugin';
4
5
  import packageJson from 'eslint-plugin-package-json';
5
6
  import { Linter, ESLint } from 'eslint';
@@ -149,7 +150,7 @@ var eslint_config = antfu(
149
150
  rules: {
150
151
  "saasmakers/ts-format-layout": "error",
151
152
  "saasmakers/ts-format-tests": "error",
152
- "saasmakers/vue-format-i18n": "error",
153
+ "saasmakers/vue-format-i18n": ["error", { locales: localeCodes }],
153
154
  "saasmakers/vue-format-script": "error",
154
155
  "saasmakers/vue-format-template": "error"
155
156
  }
@@ -1,5 +1,6 @@
1
1
  import antfu from '@antfu/eslint-config';
2
2
  import saasmakers from '@saasmakers/eslint';
3
+ import { localeCodes } from '@saasmakers/shared';
3
4
  import vitest from '@vitest/eslint-plugin';
4
5
  import packageJson from 'eslint-plugin-package-json';
5
6
  import { Linter, ESLint } from 'eslint';
@@ -149,7 +150,7 @@ var eslint_config = antfu(
149
150
  rules: {
150
151
  "saasmakers/ts-format-layout": "error",
151
152
  "saasmakers/ts-format-tests": "error",
152
- "saasmakers/vue-format-i18n": "error",
153
+ "saasmakers/vue-format-i18n": ["error", { locales: localeCodes }],
153
154
  "saasmakers/vue-format-script": "error",
154
155
  "saasmakers/vue-format-template": "error"
155
156
  }
@@ -1,5 +1,6 @@
1
1
  import antfu from '@antfu/eslint-config';
2
2
  import saasmakers from '@saasmakers/eslint';
3
+ import { localeCodes } from '@saasmakers/shared';
3
4
  import vitest from '@vitest/eslint-plugin';
4
5
  import packageJson from 'eslint-plugin-package-json';
5
6
  import { Linter, ESLint } from 'eslint';
@@ -149,7 +150,7 @@ var eslint_config = antfu(
149
150
  rules: {
150
151
  "saasmakers/ts-format-layout": "error",
151
152
  "saasmakers/ts-format-tests": "error",
152
- "saasmakers/vue-format-i18n": "error",
153
+ "saasmakers/vue-format-i18n": ["error", { locales: localeCodes }],
153
154
  "saasmakers/vue-format-script": "error",
154
155
  "saasmakers/vue-format-template": "error"
155
156
  }
@@ -1,5 +1,6 @@
1
1
  import antfu from '@antfu/eslint-config';
2
2
  import saasmakers from '@saasmakers/eslint';
3
+ import { localeCodes } from '@saasmakers/shared';
3
4
  import vitest from '@vitest/eslint-plugin';
4
5
  import packageJson from 'eslint-plugin-package-json';
5
6
  import { d as distExports } from './shared/eslint.DOaqyhfZ.mjs';
@@ -10843,7 +10844,7 @@ const eslint_config = antfu(
10843
10844
  rules: {
10844
10845
  "saasmakers/ts-format-layout": "error",
10845
10846
  "saasmakers/ts-format-tests": "error",
10846
- "saasmakers/vue-format-i18n": "error",
10847
+ "saasmakers/vue-format-i18n": ["error", { locales: localeCodes }],
10847
10848
  "saasmakers/vue-format-script": "error",
10848
10849
  "saasmakers/vue-format-template": "error"
10849
10850
  }
package/dist/index.cjs CHANGED
@@ -281,6 +281,9 @@ const rule$4 = {
281
281
  type: "layout"
282
282
  },
283
283
  create(context) {
284
+ if (context.filename.endsWith(".d.ts")) {
285
+ return {};
286
+ }
284
287
  const sourceCode = context.sourceCode;
285
288
  const maxCharacters = context.options[0]?.maxCharacters ?? defaultMaxCharacters;
286
289
  const maxLineLength = context.options[0]?.maxLineLength ?? defaultMaxLineLength;
@@ -805,6 +808,11 @@ const templateRegex = /<template>([\s\S]*)<\/template>/i;
805
808
  const templateTagLength = "<template>".length;
806
809
  const propsPrefixRegex = /\$?props\.(\w+)/g;
807
810
  const trueAttributeRegex = /(?<![\w-]):?(?!aria-)([a-z0-9-]+)="true"/gi;
811
+ const eventHandlerRegex = /(?:@|v-on:)[^\s="'<>/]+\s*=\s*"([^"]*)"/g;
812
+ const bareIdentifierRegex = /^[A-Za-z_$][\w$]*$/;
813
+ const singleCallRegex = /^([A-Za-z_$][\w$]*)\s*\(.*\)$/s;
814
+ const onPrefixRegex = /^on[A-Z]/;
815
+ const exemptCallees = /* @__PURE__ */ new Set(["$emit", "emit"]);
808
816
  const dollarTPatterns = [
809
817
  {
810
818
  pattern: / \$t\(/g,
@@ -830,10 +838,11 @@ const dollarTPatterns = [
830
838
  const rule = {
831
839
  defaultOptions: [],
832
840
  meta: {
833
- docs: { description: 'Format Vue templates: strip unnecessary props/$props prefixes, redundant ="true" attributes (except aria- attributes), and enforce t() over $t()' },
841
+ docs: { description: 'Format Vue templates: strip unnecessary props/$props prefixes, redundant ="true" attributes (except aria- attributes), enforce t() over $t(), and require "on" prefix on named event listener handlers' },
834
842
  fixable: "code",
835
843
  messages: {
836
844
  noPropsPrefix: "Unnecessary props/$props prefix in template. Props are automatically available in template scope.",
845
+ prefixEventHandlerWithOn: 'Event listener handler "{{name}}" must be prefixed with "on" (e.g. @click="onClick").',
837
846
  removeTrueAttribute: 'Unnecessary ="true" attribute. Use the attribute name directly instead.',
838
847
  useT: "Use t() instead of $t() in Vue templates as it does not work with <i18n> tags."
839
848
  },
@@ -908,6 +917,33 @@ const rule = {
908
917
  dollarTMatch = pattern.exec(templateContent);
909
918
  }
910
919
  }
920
+ eventHandlerRegex.lastIndex = 0;
921
+ let eventHandlerMatch = eventHandlerRegex.exec(templateContent);
922
+ while (eventHandlerMatch !== null) {
923
+ const handlerValue = eventHandlerMatch[1].trim();
924
+ let handlerName;
925
+ if (bareIdentifierRegex.test(handlerValue)) {
926
+ handlerName = handlerValue;
927
+ } else {
928
+ const singleCallMatch = singleCallRegex.exec(handlerValue);
929
+ if (singleCallMatch) {
930
+ handlerName = singleCallMatch[1];
931
+ }
932
+ }
933
+ if (handlerName && !exemptCallees.has(handlerName) && !onPrefixRegex.test(handlerName)) {
934
+ const valueStart = templateStartIndex + eventHandlerMatch.index + eventHandlerMatch[0].length - 1 - eventHandlerMatch[1].length;
935
+ const valueEnd = valueStart + eventHandlerMatch[1].length;
936
+ context.report({
937
+ data: { name: handlerName },
938
+ loc: {
939
+ end: sourceCode.getLocFromIndex(valueEnd),
940
+ start: sourceCode.getLocFromIndex(valueStart)
941
+ },
942
+ messageId: "prefixEventHandlerWithOn"
943
+ });
944
+ }
945
+ eventHandlerMatch = eventHandlerRegex.exec(templateContent);
946
+ }
911
947
  }
912
948
  };
913
949
  }
package/dist/index.d.cts CHANGED
@@ -5203,7 +5203,7 @@ declare const _default: {
5203
5203
  locales?: string[];
5204
5204
  } | undefined)?], unknown, RuleListener>;
5205
5205
  'vue-format-script': RuleModule<"multilineComputed" | "untypedEmits", [], unknown, RuleListener>;
5206
- 'vue-format-template': RuleModule<"noPropsPrefix" | "removeTrueAttribute" | "useT", [], unknown, RuleListener>;
5206
+ 'vue-format-template': RuleModule<"noPropsPrefix" | "prefixEventHandlerWithOn" | "removeTrueAttribute" | "useT", [], unknown, RuleListener>;
5207
5207
  };
5208
5208
  };
5209
5209
 
package/dist/index.d.mts CHANGED
@@ -5203,7 +5203,7 @@ declare const _default: {
5203
5203
  locales?: string[];
5204
5204
  } | undefined)?], unknown, RuleListener>;
5205
5205
  'vue-format-script': RuleModule<"multilineComputed" | "untypedEmits", [], unknown, RuleListener>;
5206
- 'vue-format-template': RuleModule<"noPropsPrefix" | "removeTrueAttribute" | "useT", [], unknown, RuleListener>;
5206
+ 'vue-format-template': RuleModule<"noPropsPrefix" | "prefixEventHandlerWithOn" | "removeTrueAttribute" | "useT", [], unknown, RuleListener>;
5207
5207
  };
5208
5208
  };
5209
5209
 
package/dist/index.d.ts CHANGED
@@ -5203,7 +5203,7 @@ declare const _default: {
5203
5203
  locales?: string[];
5204
5204
  } | undefined)?], unknown, RuleListener>;
5205
5205
  'vue-format-script': RuleModule<"multilineComputed" | "untypedEmits", [], unknown, RuleListener>;
5206
- 'vue-format-template': RuleModule<"noPropsPrefix" | "removeTrueAttribute" | "useT", [], unknown, RuleListener>;
5206
+ 'vue-format-template': RuleModule<"noPropsPrefix" | "prefixEventHandlerWithOn" | "removeTrueAttribute" | "useT", [], unknown, RuleListener>;
5207
5207
  };
5208
5208
  };
5209
5209
 
package/dist/index.mjs CHANGED
@@ -279,6 +279,9 @@ const rule$4 = {
279
279
  type: "layout"
280
280
  },
281
281
  create(context) {
282
+ if (context.filename.endsWith(".d.ts")) {
283
+ return {};
284
+ }
282
285
  const sourceCode = context.sourceCode;
283
286
  const maxCharacters = context.options[0]?.maxCharacters ?? defaultMaxCharacters;
284
287
  const maxLineLength = context.options[0]?.maxLineLength ?? defaultMaxLineLength;
@@ -803,6 +806,11 @@ const templateRegex = /<template>([\s\S]*)<\/template>/i;
803
806
  const templateTagLength = "<template>".length;
804
807
  const propsPrefixRegex = /\$?props\.(\w+)/g;
805
808
  const trueAttributeRegex = /(?<![\w-]):?(?!aria-)([a-z0-9-]+)="true"/gi;
809
+ const eventHandlerRegex = /(?:@|v-on:)[^\s="'<>/]+\s*=\s*"([^"]*)"/g;
810
+ const bareIdentifierRegex = /^[A-Za-z_$][\w$]*$/;
811
+ const singleCallRegex = /^([A-Za-z_$][\w$]*)\s*\(.*\)$/s;
812
+ const onPrefixRegex = /^on[A-Z]/;
813
+ const exemptCallees = /* @__PURE__ */ new Set(["$emit", "emit"]);
806
814
  const dollarTPatterns = [
807
815
  {
808
816
  pattern: / \$t\(/g,
@@ -828,10 +836,11 @@ const dollarTPatterns = [
828
836
  const rule = {
829
837
  defaultOptions: [],
830
838
  meta: {
831
- docs: { description: 'Format Vue templates: strip unnecessary props/$props prefixes, redundant ="true" attributes (except aria- attributes), and enforce t() over $t()' },
839
+ docs: { description: 'Format Vue templates: strip unnecessary props/$props prefixes, redundant ="true" attributes (except aria- attributes), enforce t() over $t(), and require "on" prefix on named event listener handlers' },
832
840
  fixable: "code",
833
841
  messages: {
834
842
  noPropsPrefix: "Unnecessary props/$props prefix in template. Props are automatically available in template scope.",
843
+ prefixEventHandlerWithOn: 'Event listener handler "{{name}}" must be prefixed with "on" (e.g. @click="onClick").',
835
844
  removeTrueAttribute: 'Unnecessary ="true" attribute. Use the attribute name directly instead.',
836
845
  useT: "Use t() instead of $t() in Vue templates as it does not work with <i18n> tags."
837
846
  },
@@ -906,6 +915,33 @@ const rule = {
906
915
  dollarTMatch = pattern.exec(templateContent);
907
916
  }
908
917
  }
918
+ eventHandlerRegex.lastIndex = 0;
919
+ let eventHandlerMatch = eventHandlerRegex.exec(templateContent);
920
+ while (eventHandlerMatch !== null) {
921
+ const handlerValue = eventHandlerMatch[1].trim();
922
+ let handlerName;
923
+ if (bareIdentifierRegex.test(handlerValue)) {
924
+ handlerName = handlerValue;
925
+ } else {
926
+ const singleCallMatch = singleCallRegex.exec(handlerValue);
927
+ if (singleCallMatch) {
928
+ handlerName = singleCallMatch[1];
929
+ }
930
+ }
931
+ if (handlerName && !exemptCallees.has(handlerName) && !onPrefixRegex.test(handlerName)) {
932
+ const valueStart = templateStartIndex + eventHandlerMatch.index + eventHandlerMatch[0].length - 1 - eventHandlerMatch[1].length;
933
+ const valueEnd = valueStart + eventHandlerMatch[1].length;
934
+ context.report({
935
+ data: { name: handlerName },
936
+ loc: {
937
+ end: sourceCode.getLocFromIndex(valueEnd),
938
+ start: sourceCode.getLocFromIndex(valueStart)
939
+ },
940
+ messageId: "prefixEventHandlerWithOn"
941
+ });
942
+ }
943
+ eventHandlerMatch = eventHandlerRegex.exec(templateContent);
944
+ }
909
945
  }
910
946
  };
911
947
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saasmakers/eslint",
3
- "version": "1.0.21",
3
+ "version": "1.0.23",
4
4
  "private": false,
5
5
  "description": "Shared ESLint config and rules for SaaS Makers projects",
6
6
  "license": "MIT",
@@ -32,7 +32,8 @@
32
32
  "eslint-plugin-package-json": "0.31.0",
33
33
  "eslint-plugin-turbo": "2.9.18",
34
34
  "eslint-plugin-zod": "3.4.0",
35
- "typescript-eslint": "8.61.0"
35
+ "typescript-eslint": "8.61.0",
36
+ "@saasmakers/shared": "0.2.8"
36
37
  },
37
38
  "devDependencies": {
38
39
  "@eslint/config-inspector": "1.4.2",