eslint-plugin-flawless 1.0.0 → 1.1.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.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { TSESLint } from "@typescript-eslint/utils";
2
+ import ts9 from "typescript";
2
3
  import { Linter } from "eslint";
3
4
  //#region src/util.d.ts
4
5
  interface PluginDocumentation {
@@ -207,6 +208,22 @@ interface MatchRegex {
207
208
  match: boolean;
208
209
  regex: string;
209
210
  }
211
+ /**
212
+ * Object-form entry in a selector's `types` array. At least one of `name` or
213
+ * `returns` must be present (enforced by the rule schema).
214
+ */
215
+ interface TypeReference {
216
+ /** Symbol name the value's type must resolve to. */
217
+ name?: string;
218
+ /** Module specifier the matched symbol must be declared in. */
219
+ from?: string;
220
+ /**
221
+ * Matches callable types by return type: at least one call signature's
222
+ * return type must satisfy this nested matcher.
223
+ */
224
+ returns?: TypeReference;
225
+ }
226
+ type TypeMatcher = TypeModifierString | TypeReference;
210
227
  interface NamingSelector {
211
228
  custom?: MatchRegex;
212
229
  filter?: MatchRegex | string;
@@ -223,7 +240,7 @@ interface NamingSelector {
223
240
  selector: Array<IndividualAndMetaSelectorsString> | IndividualAndMetaSelectorsString;
224
241
  suffix?: Array<string>;
225
242
  trailingUnderscore?: UnderscoreOptionString;
226
- types?: Array<TypeModifierString>;
243
+ types?: Array<TypeMatcher>;
227
244
  }
228
245
  //#endregion
229
246
  //#region src/rules/naming-convention/rule.d.ts
@@ -403,4 +420,4 @@ type RuleOptions = { [K in keyof RuleDefinitions]: NonNullable<RuleDefinitions[K
403
420
  type Rules = { [K in keyof RuleOptions]: Linter.RuleEntry<RuleOptions[K]>; };
404
421
  type RuleDefinitions = typeof plugin.rules;
405
422
  //#endregion
406
- export { RuleOptions, Rules, _default as default };
423
+ export { RuleOptions, Rules, type TypeMatcher, type TypeReference, _default as default };
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as plugin, t as PLUGIN_NAME } from "./plugin-DFLOyhkl.mjs";
1
+ import { n as plugin, t as PLUGIN_NAME } from "./plugin-B-YWsemf.mjs";
2
2
  //#region src/configs/index.ts
3
3
  const configs = { recommended: {
4
4
  plugins: { [PLUGIN_NAME]: plugin },
package/dist/oxlint.mjs CHANGED
@@ -1,4 +1,4 @@
1
- import { n as plugin, t as PLUGIN_NAME } from "./plugin-DFLOyhkl.mjs";
1
+ import { n as plugin, t as PLUGIN_NAME } from "./plugin-B-YWsemf.mjs";
2
2
  import { definePlugin } from "@oxlint/plugins";
3
3
  //#region src/oxlint.ts
4
4
  /**
@@ -6,16 +6,15 @@ import { fileURLToPath } from "node:url";
6
6
  import { createSyncFn } from "synckit";
7
7
  import { RuleCreator, getParserServices } from "@typescript-eslint/utils/eslint-utils";
8
8
  import { findVariable, isArrowToken, isOpeningParenToken } from "@typescript-eslint/utils/ast-utils";
9
- import { DefinitionType, ImplicitLibVariable, PatternVisitor, ScopeType, Visitor } from "@typescript-eslint/scope-manager";
10
- import { requiresQuoting } from "@typescript-eslint/type-utils";
9
+ import ts9, { ScriptTarget, TypeFlags, isIdentifierPart, isIdentifierStart } from "typescript";
10
+ import { DefinitionType, ImplicitLibVariable, ScopeType, Visitor } from "@typescript-eslint/scope-manager";
11
11
  import assert from "node:assert";
12
12
  import { getStaticJSONValue, parseForESLint } from "jsonc-eslint-parser";
13
13
  import * as core from "@eslint-react/core";
14
- import ts9 from "typescript";
15
14
  import { getStaticTOMLValue } from "toml-eslint-parser";
16
15
  //#region package.json
17
16
  var name = "eslint-plugin-flawless";
18
- var version = "1.0.0";
17
+ var version = "1.1.0";
19
18
  var repository = {
20
19
  "url": "git+https://github.com/christopher-buss/eslint-plugin-flawless.git",
21
20
  "type": "git"
@@ -1539,6 +1538,77 @@ function isUsedVariable(variable) {
1539
1538
  });
1540
1539
  }
1541
1540
  //#endregion
1541
+ //#region src/rules/naming-convention/utils/contextual-type.ts
1542
+ /**
1543
+ * Determines if an object literal member's name is dictated by the contextual
1544
+ * type of the enclosing object literal (e.g. `{ ... } satisfies
1545
+ * Partial<Service>`). In that case the name is not the author's choice - it is
1546
+ * required by the declared type, which is itself validated at its declaration
1547
+ * site - so naming validation should be skipped.
1548
+ *
1549
+ * @param node - The non-computed object literal property or method node.
1550
+ * @param services - Parser services (may lack type information).
1551
+ * @param cache - Per-file cache of contextual types keyed by object literal.
1552
+ * @returns True if the member name is required by a contextual type.
1553
+ */
1554
+ function isDictatedByContextualType(node, services, cache) {
1555
+ if (!services.program || node.parent.type !== AST_NODE_TYPES.ObjectExpression) return false;
1556
+ const checker = services.program.getTypeChecker();
1557
+ const tsObject = services.esTreeNodeToTSNodeMap.get(node.parent);
1558
+ const candidates = getCandidateTypes(node.parent, tsObject, checker, cache);
1559
+ if (candidates === null) return false;
1560
+ const name = node.key.type === AST_NODE_TYPES.Identifier ? node.key.name : String(node.key.value);
1561
+ return candidates.some((candidate) => {
1562
+ const property = checker.getPropertyOfType(candidate, name);
1563
+ return property !== void 0 && !isDeclaredWithinLiteral(property, tsObject);
1564
+ });
1565
+ }
1566
+ /**
1567
+ * Resolves the contextual type of an object literal into its candidate
1568
+ * constituent types, cached per object literal.
1569
+ *
1570
+ * Union arms are checked individually because `getPropertyOfType` on a union
1571
+ * only finds properties present in every arm, while a name dictated by a
1572
+ * single arm still isn't the author's choice.
1573
+ *
1574
+ * @param objectNode - The enclosing object literal (ESTree).
1575
+ * @param tsObject - The corresponding TypeScript node.
1576
+ * @param checker - The type checker.
1577
+ * @param cache - Per-file cache of contextual types keyed by object literal.
1578
+ * @returns The candidate types, or null if there is no contextual type.
1579
+ */
1580
+ function getCandidateTypes(objectNode, tsObject, checker, cache) {
1581
+ const cached = cache.get(objectNode);
1582
+ if (cached !== void 0) return cached;
1583
+ const contextualType = checker.getContextualType(tsObject);
1584
+ let candidates = null;
1585
+ if (contextualType !== void 0) {
1586
+ const nonNullable = contextualType.getNonNullableType();
1587
+ candidates = (nonNullable.isUnion() ? nonNullable.types : [nonNullable]).map((arm) => checker.getApparentType(arm));
1588
+ }
1589
+ cache.set(objectNode, candidates);
1590
+ return candidates;
1591
+ }
1592
+ /**
1593
+ * Guards against self-inference: in generic calls like `identity({ Name: 1 })`
1594
+ * the contextual type is inferred from the literal itself, so the property
1595
+ * symbol's declarations all live inside the literal - the name is still the
1596
+ * author's choice. Transient symbols without declarations (such as those from
1597
+ * mapped types like `Partial<T>`) originate from the contextual type and count
1598
+ * as dictated.
1599
+ *
1600
+ * @param symbol - The property symbol found on a candidate contextual type.
1601
+ * @param literal - The object literal being checked.
1602
+ * @returns True if every declaration of the symbol lies inside the literal.
1603
+ */
1604
+ function isDeclaredWithinLiteral(symbol, literal) {
1605
+ const declarations = symbol.declarations ?? [];
1606
+ if (declarations.length === 0) return false;
1607
+ return declarations.every((declaration) => {
1608
+ return declaration.getSourceFile() === literal.getSourceFile() && declaration.pos >= literal.pos && declaration.end <= literal.end;
1609
+ });
1610
+ }
1611
+ //#endregion
1542
1612
  //#region src/rules/naming-convention/utils/enums.ts
1543
1613
  const Selector = {
1544
1614
  variable: 1,
@@ -1607,6 +1677,8 @@ const TypeModifier = {
1607
1677
  array: 2097152
1608
1678
  };
1609
1679
  const TypeModifierValueToKey = Object.fromEntries(Object.entries(TypeModifier).map(([key, value]) => [value, key]));
1680
+ const TYPE_REFERENCE_LOOSE_WEIGHT = 1 << 22;
1681
+ const TYPE_REFERENCE_STRICT_WEIGHT = 1 << 23;
1610
1682
  const UnderscoreOption = {
1611
1683
  forbid: 1,
1612
1684
  allow: 2,
@@ -1887,31 +1959,130 @@ function createValidator(type, context, allConfigs) {
1887
1959
  return false;
1888
1960
  }
1889
1961
  }
1890
- const SelectorsAllowedToHaveTypes = Selector.variable | Selector.parameter | Selector.classProperty | Selector.objectLiteralProperty | Selector.typeProperty | Selector.parameterProperty | Selector.classicAccessor;
1962
+ const SelectorsAllowedToHaveTypes = Selector.variable | Selector.function | Selector.parameter | Selector.classProperty | Selector.objectLiteralProperty | Selector.typeProperty | Selector.parameterProperty | Selector.classicAccessor | Selector.classMethod | Selector.objectLiteralMethod | Selector.typeMethod;
1891
1963
  function isAllTypesMatch(type, callback) {
1892
1964
  if (type.isUnion()) return type.types.every((inner) => callback(inner));
1893
1965
  return callback(type);
1894
1966
  }
1967
+ function isAnyType(type) {
1968
+ return (type.flags & TypeFlags.Any) !== 0;
1969
+ }
1970
+ function symbolMatchesTypeReference(symbol, reference) {
1971
+ if (symbol === void 0 || reference.name === void 0 || symbol.name !== reference.name) return false;
1972
+ if (reference.from === void 0) return true;
1973
+ const { declarations } = symbol;
1974
+ if (!declarations || declarations.length === 0) return false;
1975
+ for (const declaration of declarations) {
1976
+ const { fileName } = declaration.getSourceFile();
1977
+ if (moduleSpecifierMatches(fileName, reference.from)) return true;
1978
+ }
1979
+ return false;
1980
+ }
1981
+ function matchesNamedTypeReference(type, reference) {
1982
+ if (isAnyType(type)) return false;
1983
+ if (symbolMatchesTypeReference(type.aliasSymbol, reference)) return true;
1984
+ if (symbolMatchesTypeReference(type.symbol, reference)) return true;
1985
+ if (type.isIntersection() || type.isUnion()) {
1986
+ for (const inner of type.types) if (matchesNamedTypeReference(inner, reference)) return true;
1987
+ }
1988
+ return false;
1989
+ }
1990
+ function matchesReturnTypeReference(type, reference) {
1991
+ if (isAnyType(type)) return false;
1992
+ for (const signature of type.getCallSignatures()) if (matchesTypeReference(signature.getReturnType(), reference)) return true;
1993
+ if (type.isIntersection() || type.isUnion()) {
1994
+ for (const inner of type.types) if (matchesReturnTypeReference(inner, reference)) return true;
1995
+ }
1996
+ return false;
1997
+ }
1998
+ function matchesTypeReference(type, reference) {
1999
+ if (isAnyType(type)) return false;
2000
+ if (reference.name === void 0 && reference.returns === void 0) return false;
2001
+ if (reference.name !== void 0 && !matchesNamedTypeReference(type, reference)) return false;
2002
+ if (reference.returns !== void 0 && !matchesReturnTypeReference(type, reference.returns)) return false;
2003
+ return true;
2004
+ }
1895
2005
  function isCorrectType(node, config, context, selector) {
1896
2006
  if (config.types === void 0) return true;
1897
2007
  if ((SelectorsAllowedToHaveTypes & selector) === 0) return true;
1898
2008
  const services = getParserServices(context);
1899
2009
  const checker = services.program.getTypeChecker();
1900
2010
  const type = services.getTypeAtLocation(node).getNonNullableType();
1901
- for (const allowedType of config.types) switch (allowedType) {
1902
- case TypeModifier.array:
1903
- if (isAllTypesMatch(type, (inner) => checker.isArrayType(inner) || checker.isTupleType(inner))) return true;
1904
- break;
1905
- case TypeModifier.boolean:
1906
- case TypeModifier.number:
1907
- case TypeModifier.string:
1908
- if (checker.typeToString(checker.getWidenedType(checker.getBaseTypeOfLiteralType(type))) === TypeModifierValueToKey[allowedType]) return true;
1909
- break;
1910
- case TypeModifier.function:
1911
- if (isAllTypesMatch(type, (inner) => inner.getCallSignatures().length > 0)) return true;
1912
- break;
2011
+ const predicates = [];
2012
+ for (const allowedType of config.types) {
2013
+ if (typeof allowedType === "object") {
2014
+ predicates.push((inner) => matchesTypeReference(inner, allowedType));
2015
+ continue;
2016
+ }
2017
+ switch (allowedType) {
2018
+ case TypeModifier.array:
2019
+ predicates.push((inner) => checker.isArrayType(inner) || checker.isTupleType(inner));
2020
+ break;
2021
+ case TypeModifier.boolean:
2022
+ case TypeModifier.number:
2023
+ case TypeModifier.string:
2024
+ predicates.push((inner) => {
2025
+ return checker.typeToString(checker.getWidenedType(checker.getBaseTypeOfLiteralType(inner))) === TypeModifierValueToKey[allowedType];
2026
+ });
2027
+ break;
2028
+ case TypeModifier.function:
2029
+ predicates.push((inner) => inner.getCallSignatures().length > 0);
2030
+ break;
2031
+ }
1913
2032
  }
1914
- return false;
2033
+ return isAllTypesMatch(type, (inner) => predicates.some((predicate) => predicate(inner)));
2034
+ }
2035
+ const BACKSLASH_PATTERN = /\\/gu;
2036
+ const TYPESCRIPT_EXTENSION_PATTERN = /\.d\.ts$|\.tsx?$/u;
2037
+ const WINDOWS_DRIVE_PATTERN = /^[A-Za-z]:\//u;
2038
+ const LEADING_DOT_OR_SLASH_PATTERN = /^(\.\/|\/)/u;
2039
+ /**
2040
+ * Path-form specifiers start with `.`, `/`, or a Windows drive letter (e.g.
2041
+ * `C:/`).
2042
+ *
2043
+ * @param specifier - The module specifier to classify.
2044
+ * @returns True if the specifier should be treated as a filesystem path rather
2045
+ * than a package name.
2046
+ */
2047
+ function looksLikePath(specifier) {
2048
+ if (specifier.startsWith(".")) return true;
2049
+ if (specifier.startsWith("/")) return true;
2050
+ return WINDOWS_DRIVE_PATTERN.test(specifier);
2051
+ }
2052
+ /**
2053
+ * Checks whether a declaration's source file matches a module specifier.
2054
+ *
2055
+ * Two specifier shapes are supported:
2056
+ *
2057
+ * 1. **Bare package specifier** (e.g. `"@rbxts/jecs"`, `"lodash"`) — matches
2058
+ * when the declaration's file path contains `/node_modules/<specifier>/` as
2059
+ * a substring. Handles flat and pnpm-style layouts (pnpm paths still contain
2060
+ * a final `/node_modules/<specifier>/` segment after the virtual store
2061
+ * directory). **Not** supported: Yarn Plug'n'Play (no `node_modules` on
2062
+ * disk), vendored packages outside `node_modules`, or types provided by
2063
+ * separate `@types/*` packages.
2064
+ * 2. **Path specifier** (starts with `.`, `/`, or a Windows drive letter) —
2065
+ * matches against the normalized declaration path with `.d.ts` / `.tsx?`
2066
+ * stripped. Windows absolute paths require exact equality. POSIX-style
2067
+ * absolute or relative paths are normalized to a bare tail and matched as a
2068
+ * suffix; this means `"./shared/network"` matches a declaration at
2069
+ * `<root>/shared/network.ts`.
2070
+ *
2071
+ * @param declarationFile - Path of the file declaring the matched symbol.
2072
+ * @param specifier - The `from` module specifier from the rule config.
2073
+ * @returns True if the declaration file matches the specifier.
2074
+ */
2075
+ function moduleSpecifierMatches(declarationFile, specifier) {
2076
+ const normalizedFile = declarationFile.replace(BACKSLASH_PATTERN, "/");
2077
+ const normalizedSpecifier = specifier.replace(BACKSLASH_PATTERN, "/");
2078
+ if (looksLikePath(normalizedSpecifier)) {
2079
+ const stripped = normalizedFile.replace(TYPESCRIPT_EXTENSION_PATTERN, "");
2080
+ if (WINDOWS_DRIVE_PATTERN.test(normalizedSpecifier)) return stripped === normalizedSpecifier;
2081
+ const tail = normalizedSpecifier.replace(LEADING_DOT_OR_SLASH_PATTERN, "");
2082
+ if (stripped === tail) return true;
2083
+ return stripped.endsWith(`/${tail}`);
2084
+ }
2085
+ return normalizedFile.includes(`/node_modules/${normalizedSpecifier}/`);
1915
2086
  }
1916
2087
  //#endregion
1917
2088
  //#region src/rules/naming-convention/utils/parse-options.ts
@@ -1919,10 +2090,22 @@ function parseOptions(context) {
1919
2090
  const normalizedOptions = context.options.flatMap(normalizeOption);
1920
2091
  return Object.fromEntries(Object.keys(Selector).map((key) => [key, createValidator(key, context, normalizedOptions)]));
1921
2092
  }
2093
+ /**
2094
+ * A type-reference matcher counts as "strict" for sorting when any level of it
2095
+ * (including nested `returns` matchers) constrains the declaring module.
2096
+ *
2097
+ * @param reference - The matcher to inspect.
2098
+ * @returns True if the matcher carries a `from` constraint at any depth.
2099
+ */
2100
+ function hasFromConstraint(reference) {
2101
+ if (reference.from !== void 0) return true;
2102
+ return reference.returns !== void 0 && hasFromConstraint(reference.returns);
2103
+ }
1922
2104
  function normalizeOption(option) {
1923
2105
  let weight = 0;
1924
2106
  if (option.modifiers) for (const modifier of option.modifiers) weight |= Modifier[modifier];
1925
- if (option.types) for (const type of option.types) weight |= TypeModifier[type];
2107
+ if (option.types) for (const type of option.types) if (typeof type === "string") weight |= TypeModifier[type];
2108
+ else weight |= hasFromConstraint(type) ? TYPE_REFERENCE_STRICT_WEIGHT : TYPE_REFERENCE_LOOSE_WEIGHT;
1926
2109
  if (option.filter !== void 0) weight |= 1 << 30;
1927
2110
  const normalizedOption = {
1928
2111
  custom: option.custom ? {
@@ -1943,7 +2126,7 @@ function normalizeOption(option) {
1943
2126
  prefix: option.prefix && option.prefix.length > 0 ? option.prefix : void 0,
1944
2127
  suffix: option.suffix && option.suffix.length > 0 ? option.suffix : void 0,
1945
2128
  trailingUnderscore: option.trailingUnderscore !== void 0 ? UnderscoreOption[option.trailingUnderscore] : void 0,
1946
- types: option.types?.map((type) => TypeModifier[type]) ?? void 0
2129
+ types: option.types?.map((type) => typeof type === "string" ? TypeModifier[type] : type) ?? void 0
1947
2130
  };
1948
2131
  return (Array.isArray(option.selector) ? option.selector : [option.selector]).map((selector) => {
1949
2132
  return {
@@ -1981,9 +2164,39 @@ const $DEFS = {
1981
2164
  },
1982
2165
  type: "array"
1983
2166
  },
1984
- typeModifiers: {
2167
+ typeMatcher: { oneOf: [{
2168
+ description: "Built-in type modifier matching by widened TS type.",
1985
2169
  enum: Object.keys(TypeModifier),
1986
2170
  type: "string"
2171
+ }, { $ref: "#/$defs/typeReferenceMatcher" }] },
2172
+ typeReferenceMatcher: {
2173
+ additionalProperties: false,
2174
+ anyOf: [{
2175
+ required: ["name"],
2176
+ type: "object"
2177
+ }, {
2178
+ required: ["returns"],
2179
+ type: "object"
2180
+ }],
2181
+ dependencies: { from: ["name"] },
2182
+ description: "Type-reference matcher. Matches when the variable's type resolves to a symbol with the given `name`; if `from` is supplied, the symbol's declaration must also live in a file the specifier resolves to. `returns` instead (or additionally) matches callable types by the return type of their call signatures.",
2183
+ properties: {
2184
+ name: {
2185
+ description: "Symbol name to match (compared against the type's `aliasSymbol` then `symbol`).",
2186
+ minLength: 1,
2187
+ type: "string"
2188
+ },
2189
+ from: {
2190
+ description: "Module specifier the type must originate from. Bare package name (e.g. `@rbxts/jecs`) matches `/node_modules/<from>/` in the declaration path. Path-form (starts with `.`, `/`, or a Windows drive letter) matches the declaration path with extension stripped. Omit to match any source.",
2191
+ minLength: 1,
2192
+ type: "string"
2193
+ },
2194
+ returns: {
2195
+ $ref: "#/$defs/typeReferenceMatcher",
2196
+ description: "Nested matcher applied to call-signature return types. The type matches when at least one call signature's return type satisfies it. Lets anonymous function types (which have no symbol name) be matched, e.g. React components typed `(props: P) => ReactNode`."
2197
+ }
2198
+ },
2199
+ type: "object"
1987
2200
  },
1988
2201
  underscoreOptions: {
1989
2202
  enum: Object.keys(UnderscoreOption),
@@ -2023,7 +2236,7 @@ function selectorSchema(selectorString, allowType, modifiers) {
2023
2236
  };
2024
2237
  if (allowType) selector["types"] = {
2025
2238
  additionalItems: false,
2026
- items: { $ref: "#/$defs/typeModifiers" },
2239
+ items: { $ref: "#/$defs/typeMatcher" },
2027
2240
  type: "array"
2028
2241
  };
2029
2242
  return [{
@@ -2065,7 +2278,7 @@ function selectorsSchema() {
2065
2278
  },
2066
2279
  types: {
2067
2280
  additionalItems: false,
2068
- items: { $ref: "#/$defs/typeModifiers" },
2281
+ items: { $ref: "#/$defs/typeMatcher" },
2069
2282
  type: "array"
2070
2283
  }
2071
2284
  },
@@ -2088,7 +2301,7 @@ const SCHEMA = {
2088
2301
  "unused",
2089
2302
  "async"
2090
2303
  ]),
2091
- ...selectorSchema("function", false, [
2304
+ ...selectorSchema("function", true, [
2092
2305
  "exported",
2093
2306
  "global",
2094
2307
  "unused",
@@ -2148,7 +2361,7 @@ const SCHEMA = {
2148
2361
  "override",
2149
2362
  "async"
2150
2363
  ]),
2151
- ...selectorSchema("classMethod", false, [
2364
+ ...selectorSchema("classMethod", true, [
2152
2365
  "abstract",
2153
2366
  "private",
2154
2367
  "#private",
@@ -2159,13 +2372,13 @@ const SCHEMA = {
2159
2372
  "override",
2160
2373
  "async"
2161
2374
  ]),
2162
- ...selectorSchema("objectLiteralMethod", false, [
2375
+ ...selectorSchema("objectLiteralMethod", true, [
2163
2376
  "public",
2164
2377
  "requiresQuotes",
2165
2378
  "async"
2166
2379
  ]),
2167
- ...selectorSchema("typeMethod", false, ["public", "requiresQuotes"]),
2168
- ...selectorSchema("method", false, [
2380
+ ...selectorSchema("typeMethod", true, ["public", "requiresQuotes"]),
2381
+ ...selectorSchema("method", true, [
2169
2382
  "abstract",
2170
2383
  "private",
2171
2384
  "#private",
@@ -2258,22 +2471,13 @@ const camelCaseNamingConfig = [
2258
2471
  function create$4(contextWithoutDefaults) {
2259
2472
  const context = contextWithoutDefaults.options.length > 0 ? contextWithoutDefaults : Object.setPrototypeOf({ options: camelCaseNamingConfig }, contextWithoutDefaults);
2260
2473
  const validators = parseOptions(context);
2261
- const compilerOptions = getParserServices(context, true).program?.getCompilerOptions() ?? {};
2474
+ const services = getParserServices(context, true);
2475
+ const compilerOptions = services.program?.getCompilerOptions() ?? {};
2476
+ const contextualTypeCache = /* @__PURE__ */ new WeakMap();
2262
2477
  function handleMember(validator, { key }, modifiers) {
2263
- if (requiresQuoting$1(key, compilerOptions.target)) modifiers.add(Modifier.requiresQuotes);
2478
+ if (requiresQuoting(key, compilerOptions.target)) modifiers.add(Modifier.requiresQuotes);
2264
2479
  validator(key, modifiers);
2265
2480
  }
2266
- function getMemberModifiers(node) {
2267
- const modifiers = /* @__PURE__ */ new Set();
2268
- if ("key" in node && node.key.type === AST_NODE_TYPES.PrivateIdentifier) modifiers.add(Modifier["#private"]);
2269
- else if (node.accessibility) modifiers.add(Modifier[node.accessibility]);
2270
- else modifiers.add(Modifier.public);
2271
- if (node.static) modifiers.add(Modifier.static);
2272
- if ("readonly" in node && node.readonly) modifiers.add(Modifier.readonly);
2273
- if ("override" in node && node.override) modifiers.add(Modifier.override);
2274
- if (node.type === AST_NODE_TYPES.TSAbstractPropertyDefinition || node.type === AST_NODE_TYPES.TSAbstractMethodDefinition || node.type === AST_NODE_TYPES.TSAbstractAccessorProperty) modifiers.add(Modifier.abstract);
2275
- return modifiers;
2276
- }
2277
2481
  const { unusedVariables } = collectVariables(context);
2278
2482
  function isUnused(name, initialScope) {
2279
2483
  let variable = null;
@@ -2286,52 +2490,6 @@ function create$4(contextWithoutDefaults) {
2286
2490
  if (!variable) return false;
2287
2491
  return unusedVariables.has(variable);
2288
2492
  }
2289
- function isDestructured(id) {
2290
- return id.parent.type === AST_NODE_TYPES.Property && id.parent.shorthand || id.parent.type === AST_NODE_TYPES.AssignmentPattern && id.parent.parent.type === AST_NODE_TYPES.Property && id.parent.parent.shorthand;
2291
- }
2292
- function isAsyncMemberOrProperty(propertyOrMemberNode) {
2293
- return Boolean("value" in propertyOrMemberNode && propertyOrMemberNode.value && "async" in propertyOrMemberNode.value && propertyOrMemberNode.value.async);
2294
- }
2295
- function isAsyncVariableIdentifier(id) {
2296
- return Boolean("async" in id.parent && id.parent.async || "init" in id.parent && id.parent.init && "async" in id.parent.init && id.parent.init.async);
2297
- }
2298
- /**
2299
- * Determines if a VariableDeclarator represents an object-style enum declaration.
2300
- *
2301
- * Object-style enums are const assertions applied to object expressions, commonly
2302
- * used as an alternative to TypeScript enums. Examples:
2303
- * - `const Colors = { RED: 'red', BLUE: 'blue' } as const`
2304
- * - `const Status = <const>{ OK: 200, ERROR: 500 }`.
2305
- *
2306
- * @param node - The VariableDeclarator AST node to check.
2307
- * @param parent - The parent VariableDeclaration node.
2308
- * @returns True if this represents an object-style enum declaration.
2309
- */
2310
- function isObjectStyleEnumDeclaration(node, parent) {
2311
- if (parent.kind !== "const") return false;
2312
- if (!node.init) return false;
2313
- if (node.init.type === AST_NODE_TYPES.TSAsExpression && node.init.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && node.init.typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier && node.init.typeAnnotation.typeName.name === "const" && node.init.expression.type === AST_NODE_TYPES.ObjectExpression) return true;
2314
- if (node.init.type === AST_NODE_TYPES.TSTypeAssertion && node.init.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && node.init.typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier && node.init.typeAnnotation.typeName.name === "const" && node.init.expression.type === AST_NODE_TYPES.ObjectExpression) return true;
2315
- return false;
2316
- }
2317
- /**
2318
- * Checks if a method name exists in any of the implemented interfaces.
2319
- *
2320
- * @param implementsList - List of implemented interfaces.
2321
- * @param methodName - Name of the method to check.
2322
- * @param checker - TypeScript type checker.
2323
- * @param services - Parser services.
2324
- * @returns True if the method name is found in any interface.
2325
- */
2326
- function checkInterfacesForMethod(implementsList, methodName, checker, services) {
2327
- for (const implementsClause of implementsList) {
2328
- const interfaceType = checker.getTypeAtLocation(services.esTreeNodeToTSNodeMap.get(implementsClause));
2329
- if (!interfaceType.getSymbol()) continue;
2330
- const interfaceMembers = checker.getPropertiesOfType(interfaceType);
2331
- for (const member of interfaceMembers) if (member.name === methodName) return true;
2332
- }
2333
- return false;
2334
- }
2335
2493
  /**
2336
2494
  * Determines if a class method is implementing an interface method.
2337
2495
  *
@@ -2339,7 +2497,6 @@ function create$4(contextWithoutDefaults) {
2339
2497
  * @returns True if the method is implementing an interface method.
2340
2498
  */
2341
2499
  function isImplementingInterfaceMethod(node) {
2342
- const services = getParserServices(context, true);
2343
2500
  if (!services.program) return false;
2344
2501
  const checker = services.program.getTypeChecker();
2345
2502
  let parentClass = null;
@@ -2367,6 +2524,7 @@ function create$4(contextWithoutDefaults) {
2367
2524
  },
2368
2525
  ":not(ObjectPattern) > Property[computed = false][kind = \"init\"][value.type != \"ArrowFunctionExpression\"][value.type != \"FunctionExpression\"][value.type != \"TSEmptyBodyFunctionExpression\"]": {
2369
2526
  handler: (node, validator) => {
2527
+ if (isDictatedByContextualType(node, services, contextualTypeCache)) return;
2370
2528
  handleMember(validator, node, /* @__PURE__ */ new Set([Modifier.public]));
2371
2529
  },
2372
2530
  validator: validators.objectLiteralProperty
@@ -2403,6 +2561,7 @@ function create$4(contextWithoutDefaults) {
2403
2561
  "Property[computed = false][kind = \"init\"][value.type = \"TSEmptyBodyFunctionExpression\"]"
2404
2562
  ].join(", ")]: {
2405
2563
  handler: (node, validator) => {
2564
+ if (node.type === AST_NODE_TYPES.Property && isDictatedByContextualType(node, services, contextualTypeCache)) return;
2406
2565
  const modifiers = /* @__PURE__ */ new Set([Modifier.public]);
2407
2566
  if (isAsyncMemberOrProperty(node)) modifiers.add(Modifier.async);
2408
2567
  handleMember(validator, node, modifiers);
@@ -2494,7 +2653,7 @@ function create$4(contextWithoutDefaults) {
2494
2653
  "TSEnumMember": {
2495
2654
  handler: ({ id }, validator) => {
2496
2655
  const modifiers = /* @__PURE__ */ new Set();
2497
- if (requiresQuoting$1(id, compilerOptions.target)) modifiers.add(Modifier.requiresQuotes);
2656
+ if (requiresQuoting(id, compilerOptions.target)) modifiers.add(Modifier.requiresQuotes);
2498
2657
  validator(id, modifiers);
2499
2658
  },
2500
2659
  validator: validators.enumMember
@@ -2589,13 +2748,70 @@ const namingConvention = createEslintRule({
2589
2748
  type: "suggestion"
2590
2749
  }
2591
2750
  });
2751
+ /**
2752
+ * Checks if a method name exists in any of the implemented interfaces.
2753
+ *
2754
+ * @param implementsList - List of implemented interfaces.
2755
+ * @param methodName - Name of the method to check.
2756
+ * @param checker - TypeScript type checker.
2757
+ * @param services - Parser services.
2758
+ * @returns True if the method name is found in any interface.
2759
+ */
2760
+ function checkInterfacesForMethod(implementsList, methodName, checker, services) {
2761
+ for (const implementsClause of implementsList) {
2762
+ const interfaceType = checker.getTypeAtLocation(services.esTreeNodeToTSNodeMap.get(implementsClause));
2763
+ if (!interfaceType.getSymbol()) continue;
2764
+ const interfaceMembers = checker.getPropertiesOfType(interfaceType);
2765
+ for (const member of interfaceMembers) if (member.name === methodName) return true;
2766
+ }
2767
+ return false;
2768
+ }
2592
2769
  function getIdentifiersFromPattern(pattern) {
2593
2770
  const identifiers = [];
2594
- new PatternVisitor({}, pattern, (id) => {
2595
- identifiers.push(id);
2596
- }).visit(pattern);
2771
+ const stack = [pattern];
2772
+ for (const node of stack) switch (node.type) {
2773
+ case AST_NODE_TYPES.ArrayPattern:
2774
+ for (const element of node.elements) if (element !== null) stack.push(element);
2775
+ break;
2776
+ case AST_NODE_TYPES.AssignmentPattern:
2777
+ stack.push(node.left);
2778
+ break;
2779
+ case AST_NODE_TYPES.Identifier:
2780
+ identifiers.push(node);
2781
+ break;
2782
+ case AST_NODE_TYPES.ObjectPattern:
2783
+ for (const property of node.properties) stack.push(property);
2784
+ break;
2785
+ case AST_NODE_TYPES.Property:
2786
+ stack.push(node.value);
2787
+ break;
2788
+ case AST_NODE_TYPES.RestElement:
2789
+ stack.push(node.argument);
2790
+ break;
2791
+ default: break;
2792
+ }
2597
2793
  return identifiers;
2598
2794
  }
2795
+ function getMemberModifiers(node) {
2796
+ const modifiers = /* @__PURE__ */ new Set();
2797
+ if ("key" in node && node.key.type === AST_NODE_TYPES.PrivateIdentifier) modifiers.add(Modifier["#private"]);
2798
+ else if (node.accessibility) modifiers.add(Modifier[node.accessibility]);
2799
+ else modifiers.add(Modifier.public);
2800
+ if (node.static) modifiers.add(Modifier.static);
2801
+ if ("readonly" in node && node.readonly) modifiers.add(Modifier.readonly);
2802
+ if ("override" in node && node.override) modifiers.add(Modifier.override);
2803
+ if (node.type === AST_NODE_TYPES.TSAbstractPropertyDefinition || node.type === AST_NODE_TYPES.TSAbstractMethodDefinition || node.type === AST_NODE_TYPES.TSAbstractAccessorProperty) modifiers.add(Modifier.abstract);
2804
+ return modifiers;
2805
+ }
2806
+ function isAsyncMemberOrProperty(propertyOrMemberNode) {
2807
+ return Boolean("value" in propertyOrMemberNode && propertyOrMemberNode.value && "async" in propertyOrMemberNode.value && propertyOrMemberNode.value.async);
2808
+ }
2809
+ function isAsyncVariableIdentifier(id) {
2810
+ return Boolean("async" in id.parent && id.parent.async || "init" in id.parent && id.parent.init && "async" in id.parent.init && id.parent.init.async);
2811
+ }
2812
+ function isDestructured(id) {
2813
+ return id.parent.type === AST_NODE_TYPES.Property && id.parent.shorthand || id.parent.type === AST_NODE_TYPES.AssignmentPattern && id.parent.parent.type === AST_NODE_TYPES.Property && id.parent.parent.shorthand;
2814
+ }
2599
2815
  function isExported(node, name, scope) {
2600
2816
  if (node?.parent?.type === AST_NODE_TYPES.ExportDefaultDeclaration || node?.parent?.type === AST_NODE_TYPES.ExportNamedDeclaration) return true;
2601
2817
  if (scope === null) return false;
@@ -2610,8 +2826,39 @@ function isGlobal(scope) {
2610
2826
  if (scope === null) return false;
2611
2827
  return scope.type === TSESLint.Scope.ScopeType.global || scope.type === TSESLint.Scope.ScopeType.module;
2612
2828
  }
2613
- function requiresQuoting$1(node, target) {
2614
- return requiresQuoting(node.type === AST_NODE_TYPES.Identifier || node.type === AST_NODE_TYPES.PrivateIdentifier ? node.name : `${node.value}`, target);
2829
+ /**
2830
+ * Determines if a VariableDeclarator represents an object-style enum declaration.
2831
+ *
2832
+ * Object-style enums are const assertions applied to object expressions, commonly
2833
+ * used as an alternative to TypeScript enums. Examples:
2834
+ * - `const Colors = { RED: 'red', BLUE: 'blue' } as const`
2835
+ * - `const Status = <const>{ OK: 200, ERROR: 500 }`.
2836
+ *
2837
+ * @param node - The VariableDeclarator AST node to check.
2838
+ * @param parent - The parent VariableDeclaration node.
2839
+ * @returns True if this represents an object-style enum declaration.
2840
+ */
2841
+ function isObjectStyleEnumDeclaration(node, parent) {
2842
+ if (parent.kind !== "const") return false;
2843
+ if (!node.init) return false;
2844
+ if (node.init.type === AST_NODE_TYPES.TSAsExpression && node.init.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && node.init.typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier && node.init.typeAnnotation.typeName.name === "const" && node.init.expression.type === AST_NODE_TYPES.ObjectExpression) return true;
2845
+ if (node.init.type === AST_NODE_TYPES.TSTypeAssertion && node.init.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference && node.init.typeAnnotation.typeName.type === AST_NODE_TYPES.Identifier && node.init.typeAnnotation.typeName.name === "const" && node.init.expression.type === AST_NODE_TYPES.ObjectExpression) return true;
2846
+ return false;
2847
+ }
2848
+ function isValidIdentifierText(name, languageVersion) {
2849
+ if (name.length === 0) return false;
2850
+ const firstCodePoint = name.codePointAt(0);
2851
+ if (firstCodePoint === void 0 || !isIdentifierStart(firstCodePoint, languageVersion)) return false;
2852
+ let index = firstCodePoint > 65535 ? 2 : 1;
2853
+ while (index < name.length) {
2854
+ const codePoint = name.codePointAt(index);
2855
+ if (codePoint === void 0 || !isIdentifierPart(codePoint, languageVersion)) return false;
2856
+ index += codePoint > 65535 ? 2 : 1;
2857
+ }
2858
+ return true;
2859
+ }
2860
+ function requiresQuoting(node, target) {
2861
+ return !isValidIdentifierText(node.type === AST_NODE_TYPES.Identifier || node.type === AST_NODE_TYPES.PrivateIdentifier ? node.name : `${node.value}`, target ?? ScriptTarget.Latest);
2615
2862
  }
2616
2863
  //#endregion
2617
2864
  //#region src/rules/no-export-default-arrow/rule.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-flawless",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Your ESLint plugin description",
5
5
  "keywords": [
6
6
  "eslint",
@@ -38,7 +38,6 @@
38
38
  "@eslint-react/core": "5.10.0",
39
39
  "@oxlint/plugins": "1.73.0",
40
40
  "@typescript-eslint/scope-manager": "8.62.1",
41
- "@typescript-eslint/type-utils": "8.62.1",
42
41
  "@typescript-eslint/utils": "8.62.1",
43
42
  "jsonc-eslint-parser": "3.1.0",
44
43
  "oxc-parser": "0.112.0",