@saasmakers/eslint 1.0.22 → 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.
package/dist/index.cjs CHANGED
@@ -1,7 +1,6 @@
1
1
  'use strict';
2
2
 
3
3
  const index$1 = require('./shared/eslint.BqRQ4tAN.cjs');
4
- const shared = require('@saasmakers/shared');
5
4
  require('eslint');
6
5
  require('eslint/use-at-your-own-risk');
7
6
 
@@ -519,7 +518,7 @@ const rule$3 = {
519
518
  }
520
519
  };
521
520
 
522
- const defaultLocales = shared.localeCodes;
521
+ const defaultLocales = ["en", "fr"];
523
522
  const indentSpaces = 2;
524
523
  const i18nRegex = /(<i18n\s+lang=["']json["']>)([\s\S]*?)<\/i18n>/i;
525
524
  const templateRegex$1 = /<template>([\s\S]*)<\/template>/i;
@@ -809,6 +808,11 @@ const templateRegex = /<template>([\s\S]*)<\/template>/i;
809
808
  const templateTagLength = "<template>".length;
810
809
  const propsPrefixRegex = /\$?props\.(\w+)/g;
811
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"]);
812
816
  const dollarTPatterns = [
813
817
  {
814
818
  pattern: / \$t\(/g,
@@ -834,10 +838,11 @@ const dollarTPatterns = [
834
838
  const rule = {
835
839
  defaultOptions: [],
836
840
  meta: {
837
- 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' },
838
842
  fixable: "code",
839
843
  messages: {
840
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").',
841
846
  removeTrueAttribute: 'Unnecessary ="true" attribute. Use the attribute name directly instead.',
842
847
  useT: "Use t() instead of $t() in Vue templates as it does not work with <i18n> tags."
843
848
  },
@@ -912,6 +917,33 @@ const rule = {
912
917
  dollarTMatch = pattern.exec(templateContent);
913
918
  }
914
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
+ }
915
947
  }
916
948
  };
917
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
@@ -1,5 +1,4 @@
1
1
  import { d as distExports } from './shared/eslint.DOaqyhfZ.mjs';
2
- import { localeCodes } from '@saasmakers/shared';
3
2
  import 'eslint';
4
3
  import 'eslint/use-at-your-own-risk';
5
4
 
@@ -517,7 +516,7 @@ const rule$3 = {
517
516
  }
518
517
  };
519
518
 
520
- const defaultLocales = localeCodes;
519
+ const defaultLocales = ["en", "fr"];
521
520
  const indentSpaces = 2;
522
521
  const i18nRegex = /(<i18n\s+lang=["']json["']>)([\s\S]*?)<\/i18n>/i;
523
522
  const templateRegex$1 = /<template>([\s\S]*)<\/template>/i;
@@ -807,6 +806,11 @@ const templateRegex = /<template>([\s\S]*)<\/template>/i;
807
806
  const templateTagLength = "<template>".length;
808
807
  const propsPrefixRegex = /\$?props\.(\w+)/g;
809
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"]);
810
814
  const dollarTPatterns = [
811
815
  {
812
816
  pattern: / \$t\(/g,
@@ -832,10 +836,11 @@ const dollarTPatterns = [
832
836
  const rule = {
833
837
  defaultOptions: [],
834
838
  meta: {
835
- 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' },
836
840
  fixable: "code",
837
841
  messages: {
838
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").',
839
844
  removeTrueAttribute: 'Unnecessary ="true" attribute. Use the attribute name directly instead.',
840
845
  useT: "Use t() instead of $t() in Vue templates as it does not work with <i18n> tags."
841
846
  },
@@ -910,6 +915,33 @@ const rule = {
910
915
  dollarTMatch = pattern.exec(templateContent);
911
916
  }
912
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
+ }
913
945
  }
914
946
  };
915
947
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@saasmakers/eslint",
3
- "version": "1.0.22",
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",
@@ -27,13 +27,13 @@
27
27
  ],
28
28
  "dependencies": {
29
29
  "@antfu/eslint-config": "7.4.3",
30
- "@saasmakers/shared": "0.2.8",
31
30
  "@unocss/eslint-plugin": "66.7.0",
32
31
  "@vitest/eslint-plugin": "1.6.20",
33
32
  "eslint-plugin-package-json": "0.31.0",
34
33
  "eslint-plugin-turbo": "2.9.18",
35
34
  "eslint-plugin-zod": "3.4.0",
36
- "typescript-eslint": "8.61.0"
35
+ "typescript-eslint": "8.61.0",
36
+ "@saasmakers/shared": "0.2.8"
37
37
  },
38
38
  "devDependencies": {
39
39
  "@eslint/config-inspector": "1.4.2",