@eagleoutice/eslint-config-flowr 2.0.2 → 2.1.3

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.
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Shared helpers of the flowR rules.
3
+ * Tags: `@useInstead <target>` names a replacement, `@lintIgnore [ids]` silences the rules.
4
+ */
5
+ import type { Rule } from 'eslint';
6
+ import type { Node } from 'estree';
7
+ import type { TsSymbol, TsTypeChecker, TypeServices } from './ts-types';
8
+ /** The type-aware services, or `undefined` if the file is parsed without a program. */
9
+ export declare function typeServices(context: Rule.RuleContext): TypeServices | undefined;
10
+ /**
11
+ * Whether `node` sits below a declaration documented with `@lintIgnore`, or in a file whose header comment
12
+ * carries it. Guarded by one string search, so untagged files pay nothing.
13
+ */
14
+ export declare function suppressed(context: Rule.RuleContext, node: Rule.Node, ids: readonly string[]): boolean;
15
+ /** The symbol an ESTree node resolves to, with import aliases followed. */
16
+ export declare function symbolOf(node: Node | undefined, services: TypeServices, checker: TsTypeChecker): TsSymbol | undefined;
17
+ /** The file a symbol is declared in. */
18
+ export declare function declarationFile(symbol: TsSymbol | undefined): string | undefined;
19
+ /** The text of the `@useInstead` tag of a symbol, `undefined` if it carries none. */
20
+ export declare function useInsteadOf(symbol: TsSymbol, checker: TsTypeChecker): string | undefined;
21
+ /**
22
+ * Whether the replacement can be applied on the spot: every name it adds over the matched code has to be
23
+ * bound already and usable as a value, a fixer cannot add the import that would be missing otherwise.
24
+ */
25
+ export declare function isApplicable(context: Rule.RuleContext, node: Node, replacement: string): boolean;
package/rules/util.js ADDED
@@ -0,0 +1,113 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.typeServices = typeServices;
4
+ exports.suppressed = suppressed;
5
+ exports.symbolOf = symbolOf;
6
+ exports.declarationFile = declarationFile;
7
+ exports.useInsteadOf = useInsteadOf;
8
+ exports.isApplicable = isApplicable;
9
+ const USE_INSTEAD_TAG = 'useInstead';
10
+ const SUPPRESS_TAG = '@lintIgnore';
11
+ /* ts.SymbolFlags.Alias, spelled out to keep typescript out of the dependencies */
12
+ const ALIAS_FLAG = 1 << 21;
13
+ /** The type-aware services, or `undefined` if the file is parsed without a program. */
14
+ function typeServices(context) {
15
+ const services = context.sourceCode.parserServices;
16
+ return services?.program && services.esTreeNodeToTSNodeMap ? services : undefined;
17
+ }
18
+ function isDocComment(comment) {
19
+ return comment.type === 'Block' && comment.value.startsWith('*');
20
+ }
21
+ /** Whether the documentation comment silences the given rule or pattern id. */
22
+ function silences(comment, ids) {
23
+ if (!isDocComment(comment)) {
24
+ return false;
25
+ }
26
+ const ignore = /@lintIgnore([^\n@*]*)/.exec(comment.value);
27
+ if (!ignore) {
28
+ return false;
29
+ }
30
+ const listed = ignore[1].split(/[\s,]+/).filter(s => s.length > 0);
31
+ return listed.length === 0 || listed.some(id => ids.includes(id));
32
+ }
33
+ /**
34
+ * Whether `node` sits below a declaration documented with `@lintIgnore`, or in a file whose header comment
35
+ * carries it. Guarded by one string search, so untagged files pay nothing.
36
+ */
37
+ function suppressed(context, node, ids) {
38
+ const sourceCode = context.sourceCode;
39
+ if (!sourceCode.text.includes(SUPPRESS_TAG)) {
40
+ return false;
41
+ }
42
+ /* a header comment marks the whole file: above the imports, or separated by a blank line */
43
+ const first = sourceCode.getAllComments()[0];
44
+ const top = sourceCode.ast.body[0];
45
+ if (first?.range && top?.range && first.range[1] < top.range[0]
46
+ && (top.type === 'ImportDeclaration' || /\n\s*\n/.test(sourceCode.text.slice(first.range[1], top.range[0])))
47
+ && silences(first, ids)) {
48
+ return true;
49
+ }
50
+ for (let current = node; current; current = current.parent) {
51
+ /* `export function f()` carries its documentation on the export */
52
+ const parent = current.parent;
53
+ const documented = parent?.type === 'ExportNamedDeclaration' || parent?.type === 'ExportDefaultDeclaration' ? parent : current;
54
+ if (sourceCode.getCommentsBefore(documented).some(c => silences(c, ids))) {
55
+ return true;
56
+ }
57
+ }
58
+ return false;
59
+ }
60
+ /** Resolves import aliases so the documentation of the original declaration is read. */
61
+ function resolveAlias(symbol, checker) {
62
+ return (symbol.flags & ALIAS_FLAG) !== 0 ? checker.getAliasedSymbol(symbol) : symbol;
63
+ }
64
+ /** The symbol an ESTree node resolves to, with import aliases followed. */
65
+ function symbolOf(node, services, checker) {
66
+ const tsNode = node ? services.esTreeNodeToTSNodeMap.get(node) : undefined;
67
+ const symbol = tsNode ? checker.getSymbolAtLocation(tsNode) : undefined;
68
+ return symbol ? resolveAlias(symbol, checker) : undefined;
69
+ }
70
+ /** The file a symbol is declared in. */
71
+ function declarationFile(symbol) {
72
+ return symbol?.declarations?.[0]?.getSourceFile()?.fileName;
73
+ }
74
+ /** The text of the `@useInstead` tag of a symbol, `undefined` if it carries none. */
75
+ function useInsteadOf(symbol, checker) {
76
+ for (const tag of symbol.getJsDocTags(checker)) {
77
+ if (tag.name === USE_INSTEAD_TAG) {
78
+ return (tag.text ?? []).map(p => p.text).join('').trim();
79
+ }
80
+ }
81
+ return undefined;
82
+ }
83
+ /** the names a chunk of code depends on: the root of every member chain, `a.b(c)` gives `a` and `c` */
84
+ function rootNames(text) {
85
+ const names = new Set();
86
+ for (const [, name] of text.matchAll(/(?:^|[^\w$.])([A-Za-z_$][\w$]*)/g)) {
87
+ names.add(name);
88
+ }
89
+ return names;
90
+ }
91
+ function isValueInScope(sourceCode, node, name) {
92
+ for (let scope = sourceCode.getScope(node); scope; scope = scope.upper) {
93
+ const variable = scope.set.get(name);
94
+ if (variable) {
95
+ /* a type-only binding of the same name is in scope but cannot be used as a value */
96
+ /* `Type` is typescript-eslint's own definition kind, which eslint's types do not know about */
97
+ return variable.defs.some(d => d.type !== 'Type'
98
+ && !(d.type === 'ImportBinding' && (isTypeOnly(d.node) || isTypeOnly(d.parent))));
99
+ }
100
+ }
101
+ return false;
102
+ }
103
+ function isTypeOnly(node) {
104
+ return node?.importKind === 'type';
105
+ }
106
+ /**
107
+ * Whether the replacement can be applied on the spot: every name it adds over the matched code has to be
108
+ * bound already and usable as a value, a fixer cannot add the import that would be missing otherwise.
109
+ */
110
+ function isApplicable(context, node, replacement) {
111
+ const present = rootNames(context.sourceCode.getText(node));
112
+ return [...rootNames(replacement)].every(name => present.has(name) || isValueInScope(context.sourceCode, node, name));
113
+ }
package/tsdoc.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "$schema": "https://developer.microsoft.com/json-schemas/tsdoc/v0/tsdoc.schema.json",
3
+ "tagDefinitions": [
4
+ {
5
+ "tagName": "@useInstead",
6
+ "syntaxKind": "block"
7
+ },
8
+ {
9
+ "tagName": "@lintIgnore",
10
+ "syntaxKind": "block"
11
+ }
12
+ ]
13
+ }