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 +19 -2
- package/dist/index.mjs +1 -1
- package/dist/oxlint.mjs +1 -1
- package/dist/{plugin-DFLOyhkl.mjs → plugin-B-YWsemf.mjs} +341 -94
- package/package.json +1 -2
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<
|
|
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
package/dist/oxlint.mjs
CHANGED
|
@@ -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 {
|
|
10
|
-
import {
|
|
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.
|
|
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
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
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
|
|
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
|
-
|
|
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/
|
|
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/
|
|
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",
|
|
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",
|
|
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",
|
|
2375
|
+
...selectorSchema("objectLiteralMethod", true, [
|
|
2163
2376
|
"public",
|
|
2164
2377
|
"requiresQuotes",
|
|
2165
2378
|
"async"
|
|
2166
2379
|
]),
|
|
2167
|
-
...selectorSchema("typeMethod",
|
|
2168
|
-
...selectorSchema("method",
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
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
|
-
|
|
2614
|
-
|
|
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.
|
|
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",
|