find-primordials 1.0.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/ignore.mjs ADDED
@@ -0,0 +1,161 @@
1
+
2
+ import * as minimatchModule from 'minimatch';
3
+
4
+ /**
5
+ * Resolve the minimatch callable across module shapes (v3 CJS default vs v10+ named export).
6
+ * @param {Record<string, unknown>} mod - The imported minimatch module namespace
7
+ * @returns {(target: string, pattern: string, options?: object) => boolean}
8
+ */
9
+ export function resolveMinimatch(mod) {
10
+ // @ts-expect-error - interop across minimatch major versions is not statically typed
11
+ return mod.minimatch || mod.default || mod;
12
+ }
13
+
14
+ /* Handle both minimatch v3 (CJS default) and v10+ (named export) */
15
+ const minimatch = resolveMinimatch(minimatchModule);
16
+
17
+ /*
18
+ * Ignore configuration format:
19
+ * {
20
+ * files: ['vendor/**'], // Glob patterns - ignore entire files
21
+ * types: ['spread', 'global'], // Finding types to ignore
22
+ * categories: ['RegExp'], // Categories to ignore
23
+ * names: ['test', 'exec'], // Method/property names to ignore
24
+ * rules: [ // Fine-grained rules
25
+ * { files: ['src/*.js'], types: ['instanceMethod'] },
26
+ * { files: ['helpers/**'], names: ['push'] },
27
+ * ]
28
+ * }
29
+ */
30
+
31
+ /** @import { Finding } from '#/analyzer' */
32
+
33
+ const VALID_TYPES = new Set(/** @type {const} */ ([
34
+ 'global',
35
+ 'instanceMethod',
36
+ 'prototypeAccess',
37
+ 'spread',
38
+ 'staticMethod',
39
+ 'staticProperty',
40
+ ]));
41
+
42
+ /** @typedef {{ categories?: string[], files?: string[], names?: string[], rules?: unknown, types?: string[] }} RawIgnoreConfig */
43
+ /** @typedef {{ categories: Set<string>, files: string[], names: Set<string>, rules: { categories: Set<string>, files: string[], names: Set<string>, types: Set<string> }[], types: Set<string> }} IgnoreConfig */
44
+
45
+ /**
46
+ * Normalize ignore config to a consistent format
47
+ * @param {RawIgnoreConfig | IgnoreConfig} config
48
+ * @returns {null | IgnoreConfig}
49
+ */
50
+ export function normalizeIgnoreConfig(config) {
51
+ if (!config) {
52
+ return null;
53
+ }
54
+
55
+ return {
56
+ categories: Array.isArray(config.categories) ? new Set(config.categories) : new Set(),
57
+ files: Array.isArray(config.files) ? config.files : [],
58
+ names: Array.isArray(config.names) ? new Set(config.names) : new Set(),
59
+ rules: Array.isArray(config.rules) ? config.rules.map((rule) => ({
60
+ categories: Array.isArray(rule.categories) ? new Set(rule.categories) : new Set(),
61
+ files: Array.isArray(rule.files) ? rule.files : [],
62
+ names: Array.isArray(rule.names) ? new Set(rule.names) : new Set(),
63
+ types: Array.isArray(rule.types) ? new Set(rule.types) : new Set(),
64
+ })) : [],
65
+ types: Array.isArray(config.types) ? new Set(config.types) : new Set(),
66
+ };
67
+ }
68
+
69
+ /**
70
+ * Check if a file path matches any of the glob patterns
71
+ * @param {string} filePath
72
+ * @param {string[]} patterns
73
+ */
74
+ function matchesAnyPattern(filePath, patterns) {
75
+ for (const pattern of patterns) {
76
+ if (minimatch(filePath, pattern, { dot: true, matchBase: true })) {
77
+ return true;
78
+ }
79
+ }
80
+ return false;
81
+ }
82
+
83
+ /**
84
+ * Check if a file should be ignored entirely
85
+ * @param {string} filePath
86
+ * @param {IgnoreConfig} [ignoreConfig]
87
+ */
88
+ export function shouldIgnoreFile(filePath, ignoreConfig) {
89
+ if (!ignoreConfig || ignoreConfig.files.length === 0) {
90
+ return false;
91
+ }
92
+ return matchesAnyPattern(filePath, ignoreConfig.files);
93
+ }
94
+
95
+ /**
96
+ * Check if a finding should be ignored
97
+ * @param {Finding} finding
98
+ * @param {IgnoreConfig} [ignoreConfig]
99
+ */
100
+ export function shouldIgnoreFinding(finding, ignoreConfig) {
101
+ if (!ignoreConfig) {
102
+ return false;
103
+ }
104
+
105
+ // Check global type filter
106
+ if (ignoreConfig.types.has(finding.type)) {
107
+ return true;
108
+ }
109
+
110
+ // Check global category filter
111
+ if (finding.category && ignoreConfig.categories.has(finding.category)) {
112
+ return true;
113
+ }
114
+
115
+ // Check global name filter
116
+ if (finding.name && ignoreConfig.names.has(finding.name)) {
117
+ return true;
118
+ }
119
+
120
+ // Check fine-grained rules
121
+ for (const rule of ignoreConfig.rules) {
122
+ // Rule must match file pattern (if specified) and have at least one filter
123
+ const fileMatches = rule.files.length === 0 || matchesAnyPattern(finding.file, rule.files);
124
+ const hasFilters = rule.types.size > 0 || rule.categories.size > 0 || rule.names.size > 0;
125
+
126
+ if (fileMatches && hasFilters && rule.files.length > 0) {
127
+ // For file-specific rules, ANY matching filter causes ignore
128
+ if (rule.types.has(finding.type)) {
129
+ return true;
130
+ }
131
+ if (finding.category && rule.categories.has(finding.category)) {
132
+ return true;
133
+ }
134
+ if (finding.name && rule.names.has(finding.name)) {
135
+ return true;
136
+ }
137
+ }
138
+ }
139
+
140
+ return false;
141
+ }
142
+
143
+ /**
144
+ * Filter an array of findings based on ignore config
145
+ * @param {Finding[]} findings
146
+ * @param {IgnoreConfig | null} [ignoreConfig]
147
+ */
148
+ export function filterFindings(findings, ignoreConfig) {
149
+ if (!ignoreConfig) {
150
+ return findings;
151
+ }
152
+ return findings.filter((finding) => !shouldIgnoreFinding(finding, ignoreConfig));
153
+ }
154
+
155
+ /**
156
+ * Get valid finding types for documentation/validation
157
+ * @returns {(typeof VALID_TYPES extends Set<infer R> ? R : never)[]}
158
+ */
159
+ export function getValidTypes() {
160
+ return [...VALID_TYPES];
161
+ }
package/index.d.mts ADDED
@@ -0,0 +1,201 @@
1
+ // Type declarations for `find-primordials`.
2
+
3
+ /** A source position, as ESTree records it. */
4
+ export type SourceLocation = {
5
+ start: { line: number; column: number };
6
+ end: { line: number; column: number };
7
+ };
8
+
9
+ /**
10
+ * A parsed ESTree/TSESTree node: one permissive shape whose fields the predicates read
11
+ * after checking `type`. A node that may be absent is {@link MaybeNode}.
12
+ */
13
+ export type ASTNode = {
14
+ type: string;
15
+ name: string;
16
+ operator: string;
17
+ kind: string;
18
+ computed: boolean;
19
+ shorthand: boolean;
20
+ object: ASTNode;
21
+ property: ASTNode;
22
+ callee: ASTNode;
23
+ left: ASTNode;
24
+ right: ASTNode;
25
+ test: ASTNode;
26
+ consequent: ASTNode;
27
+ alternate: ASTNode;
28
+ argument: ASTNode;
29
+ id: ASTNode;
30
+ init: ASTNode;
31
+ key: ASTNode;
32
+ local: ASTNode;
33
+ param: ASTNode;
34
+ arguments: ASTNode[];
35
+ properties: ASTNode[];
36
+ params: ASTNode[];
37
+ expressions: ASTNode[];
38
+ declarations: ASTNode[];
39
+ specifiers: ASTNode[];
40
+ elements: (ASTNode | null)[];
41
+ comments: ASTNode[];
42
+ value: unknown;
43
+ body: unknown;
44
+ range: [number, number];
45
+ loc: SourceLocation;
46
+ };
47
+
48
+ /** An {@link ASTNode} that may be absent. */
49
+ export type MaybeNode = ASTNode | null | undefined;
50
+
51
+ /** The finding kinds that have an autofix. */
52
+ export type FixKind = 'at' | 'constructor' | 'isNaN' | 'push' | 'undefined';
53
+
54
+ /** A finding type accepted in ignore configuration. */
55
+ export type FindingType = 'global' | 'instanceMethod' | 'prototypeAccess' | 'spread' | 'staticMethod' | 'staticProperty';
56
+
57
+ /** A primordial usage the analyzer reports. */
58
+ export type Finding = {
59
+ type: string;
60
+ name: string;
61
+ certainty: string;
62
+ file: string;
63
+ line?: number;
64
+ column?: number;
65
+ category?: string | null;
66
+ possibleCategories?: string[];
67
+ };
68
+
69
+ /** A parse/read error encountered while analyzing a file. */
70
+ export type AnalysisError = {
71
+ error: string;
72
+ file: string;
73
+ };
74
+
75
+ /** The findings and errors from analyzing one or more files. */
76
+ export type AnalysisResult = {
77
+ errors: AnalysisError[];
78
+ findings: Finding[];
79
+ };
80
+
81
+ /** Options accepted by the analyzers. */
82
+ export type AnalyzeOptions = {
83
+ includeGlobals?: boolean;
84
+ includeSpread?: boolean;
85
+ includeStatic?: boolean;
86
+ includeUncertain?: boolean;
87
+ isSafe?: boolean;
88
+ isSafeFile?: ((filePath: string) => boolean) | null;
89
+ concurrency?: number;
90
+ };
91
+
92
+ /** The result of rewriting a file. */
93
+ export type FixResult = {
94
+ fixed: boolean;
95
+ output: string;
96
+ fixCount: number;
97
+ fixCounts: Record<FixKind, number>;
98
+ };
99
+
100
+ /** Raw ignore configuration, as authored in a config file. */
101
+ export type RawIgnoreConfig = {
102
+ categories?: string[];
103
+ files?: string[];
104
+ names?: string[];
105
+ rules?: unknown;
106
+ types?: string[];
107
+ };
108
+
109
+ /** A fine-grained ignore rule within a normalized {@link IgnoreConfig}. */
110
+ export type IgnoreRule = {
111
+ categories: Set<string>;
112
+ files: string[];
113
+ names: Set<string>;
114
+ types: Set<string>;
115
+ };
116
+
117
+ /** Normalized ignore configuration. */
118
+ export type IgnoreConfig = {
119
+ categories: Set<string>;
120
+ files: string[];
121
+ names: Set<string>;
122
+ rules: IgnoreRule[];
123
+ types: Set<string>;
124
+ };
125
+
126
+ /** The slice of a TypeScript `Type` that {@link describeType} reads. */
127
+ export type TypeLike = { flags: number };
128
+
129
+ /** The slice of a TypeScript type checker that {@link describeType} reads. */
130
+ export type TypeCheckerLike<T extends TypeLike> = {
131
+ typeToString: (type: T) => string;
132
+ isArrayType?: (type: T) => boolean;
133
+ isTupleType?: (type: T) => boolean;
134
+ };
135
+
136
+ // ---- analysis ----
137
+
138
+ export function analyzeFile(filePath: string, options?: AnalyzeOptions): { error: string | null; findings: Finding[] };
139
+ export function analyzeFiles(filePaths: string[], options?: AnalyzeOptions): AnalysisResult;
140
+ export function analyzeFilesParallel(filePaths: string[], options?: AnalyzeOptions): Promise<AnalysisResult>;
141
+
142
+ // ---- fixes ----
143
+
144
+ export function applyFixes(filePath: string, findings: Finding[]): FixResult;
145
+ export function applyPushFixes(filePath: string, findings: Finding[]): FixResult;
146
+ export function applyUndefinedFixes(filePath: string, findings: Finding[]): FixResult;
147
+
148
+ // ---- fix predicates (shared with the ESLint plugin's rewrites) ----
149
+
150
+ export function isCalled(node: ASTNode, parent: MaybeNode): boolean;
151
+ export function isRepeatable(node: MaybeNode): boolean;
152
+ export function isReevaluable(node: MaybeNode): boolean;
153
+ export function literalIndex(arg: ASTNode): number | null;
154
+ export function startsAStatement(node: ASTNode, parent: MaybeNode): boolean;
155
+ export function canBeArrayLiteral(args: ASTNode[]): boolean;
156
+ export function voidNeedsParens(node: ASTNode, parent: MaybeNode): boolean;
157
+ export function canRewriteUndefined(parent: MaybeNode): boolean;
158
+
159
+ // ---- type description ----
160
+
161
+ export function describeType<T extends TypeLike>(typeChecker: TypeCheckerLike<T>, type: T): string | null;
162
+
163
+ // ---- formatting / grouping ----
164
+
165
+ export function categoryLabel(finding: Finding): string;
166
+ export function groupFindingsByCategory(findings: Finding[]): Record<string, Finding[]>;
167
+ export function formatFindingAsTAP(finding: Finding, testNum: number): string;
168
+ export function formatAsTAP(findings: Finding[], options?: { showUncertain?: boolean }): string;
169
+
170
+ // ---- ignore configuration ----
171
+
172
+ export function normalizeIgnoreConfig(config: RawIgnoreConfig | IgnoreConfig): IgnoreConfig | null;
173
+ export function shouldIgnoreFile(filePath: string, ignoreConfig?: IgnoreConfig): boolean;
174
+ export function shouldIgnoreFinding(finding: Finding, ignoreConfig?: IgnoreConfig): boolean;
175
+ export function filterFindings(findings: Finding[], ignoreConfig?: IgnoreConfig | null): Finding[];
176
+ export function getValidTypes(): FindingType[];
177
+
178
+ // ---- file classification ----
179
+
180
+ export function isTestFile(filePath: string): boolean;
181
+ export function isConfigFile(filePath: string): boolean;
182
+ export function isBinFile(filePath: string): boolean;
183
+ export function isPrivatePackage(filePath: string): boolean;
184
+ export function isUnpublishedFile(filePath: string): boolean;
185
+ export function isSafeFile(filePath: string): boolean;
186
+
187
+ /** The default file extensions the analyzers scan. */
188
+ export const defaultExtensions: string[];
189
+
190
+ // ---- primordial data (also available at `find-primordials/primordials`) ----
191
+
192
+ export {
193
+ allGlobals,
194
+ allInstanceMethods,
195
+ allStaticMethods,
196
+ ambiguousInstanceMethods,
197
+ globalToCategory,
198
+ primordials,
199
+ typedArrayGlobals,
200
+ } from './primordials.mjs';
201
+ export type { PrimordialCategory } from './primordials.mjs';