ng-checkup 0.0.0-stage → 0.2.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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +273 -2
  3. package/dist/checks/change-detection.d.ts +22 -0
  4. package/dist/checks/change-detection.js +99 -0
  5. package/dist/checks/change-detection.js.map +1 -0
  6. package/dist/checks/circular-deps.d.ts +13 -0
  7. package/dist/checks/circular-deps.js +43 -0
  8. package/dist/checks/circular-deps.js.map +1 -0
  9. package/dist/checks/components.d.ts +19 -0
  10. package/dist/checks/components.js +97 -0
  11. package/dist/checks/components.js.map +1 -0
  12. package/dist/checks/context.d.ts +51 -0
  13. package/dist/checks/context.js +53 -0
  14. package/dist/checks/context.js.map +1 -0
  15. package/dist/checks/deprecated-apis.d.ts +14 -0
  16. package/dist/checks/deprecated-apis.js +141 -0
  17. package/dist/checks/deprecated-apis.js.map +1 -0
  18. package/dist/checks/state.d.ts +13 -0
  19. package/dist/checks/state.js +72 -0
  20. package/dist/checks/state.js.map +1 -0
  21. package/dist/checks/testing.d.ts +13 -0
  22. package/dist/checks/testing.js +98 -0
  23. package/dist/checks/testing.js.map +1 -0
  24. package/dist/checks/type-safety.d.ts +14 -0
  25. package/dist/checks/type-safety.js +117 -0
  26. package/dist/checks/type-safety.js.map +1 -0
  27. package/dist/checks/version.d.ts +12 -0
  28. package/dist/checks/version.js +107 -0
  29. package/dist/checks/version.js.map +1 -0
  30. package/dist/cli.d.ts +65 -0
  31. package/dist/cli.js +285 -0
  32. package/dist/cli.js.map +1 -0
  33. package/dist/config.d.ts +47 -0
  34. package/dist/config.js +93 -0
  35. package/dist/config.js.map +1 -0
  36. package/dist/deprecations.d.ts +30 -0
  37. package/dist/deprecations.js +279 -0
  38. package/dist/deprecations.js.map +1 -0
  39. package/dist/files.d.ts +33 -0
  40. package/dist/files.js +98 -0
  41. package/dist/files.js.map +1 -0
  42. package/dist/graph.d.ts +44 -0
  43. package/dist/graph.js +151 -0
  44. package/dist/graph.js.map +1 -0
  45. package/dist/index.d.ts +44 -0
  46. package/dist/index.js +135 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/init.d.ts +78 -0
  49. package/dist/init.js +220 -0
  50. package/dist/init.js.map +1 -0
  51. package/dist/report/html.d.ts +14 -0
  52. package/dist/report/html.js +210 -0
  53. package/dist/report/html.js.map +1 -0
  54. package/dist/report/markdown.d.ts +8 -0
  55. package/dist/report/markdown.js +55 -0
  56. package/dist/report/markdown.js.map +1 -0
  57. package/dist/report/summary.d.ts +45 -0
  58. package/dist/report/summary.js +65 -0
  59. package/dist/report/summary.js.map +1 -0
  60. package/dist/report/terminal.d.ts +17 -0
  61. package/dist/report/terminal.js +70 -0
  62. package/dist/report/terminal.js.map +1 -0
  63. package/dist/scan.d.ts +119 -0
  64. package/dist/scan.js +270 -0
  65. package/dist/scan.js.map +1 -0
  66. package/dist/types.d.ts +97 -0
  67. package/dist/types.js +9 -0
  68. package/dist/types.js.map +1 -0
  69. package/dist/workspace.d.ts +76 -0
  70. package/dist/workspace.js +156 -0
  71. package/dist/workspace.js.map +1 -0
  72. package/package.json +67 -4
  73. package/schematics/collection.json +10 -0
  74. package/schematics/ng-add/index.cjs +60 -0
  75. package/schematics/ng-add/schema.json +29 -0
@@ -0,0 +1,47 @@
1
+ /** The file `ng-checkup` reads from the analysed root when no `--config` is given. */
2
+ export declare const CONFIG_FILE = "ng-checkup.config.json";
3
+ /**
4
+ * What `ng-checkup.config.json` may set. Every key is optional and every one
5
+ * has a command-line flag that overrides it, so a config file only ever
6
+ * supplies defaults: a developer running `npx ng-checkup --report x.md` gets
7
+ * exactly that, whatever the file says.
8
+ */
9
+ export interface CheckupConfig {
10
+ /** Report files to write, relative to the config file's directory. */
11
+ reports?: string[];
12
+ /** As `--fail-on`. */
13
+ failOn?: 'fair' | 'poor';
14
+ /** As `--max-examples`. */
15
+ maxExamples?: number;
16
+ /** As `--offline`. */
17
+ offline?: boolean;
18
+ /**
19
+ * Paths left out of the analysis, relative to the root: `src/legacy`,
20
+ * `**\/generated`, `apps/*\/e2e`. A pattern matches a file or a directory and
21
+ * everything below it; `*` stops at `/`, `**` does not.
22
+ */
23
+ ignore?: string[];
24
+ }
25
+ /**
26
+ * Reads and validates the config file.
27
+ *
28
+ * @param explicitPath A path given with `--config`. It must exist; the default
29
+ * file may be absent, which simply means no config.
30
+ * @returns The parsed config and the directory its relative paths resolve
31
+ * against, or an empty config when there is no file.
32
+ * @throws An `Error` whose message is meant for the user on malformed JSON,
33
+ * an unknown key or a value of the wrong type. A typo such as `failon`
34
+ * fails loudly instead of being silently ignored, which would leave CI
35
+ * passing on a threshold nobody set.
36
+ */
37
+ export declare function loadConfig(root: string, explicitPath?: string): {
38
+ config: CheckupConfig;
39
+ directory: string;
40
+ path?: string;
41
+ };
42
+ /**
43
+ * Compiles `ignore` patterns into one predicate over report paths (relative
44
+ * to the root, `/`-separated). Undefined when there are no patterns, so the
45
+ * file walk can skip the check entirely.
46
+ */
47
+ export declare function ignoreMatcher(patterns: string[] | undefined): ((relativePath: string) => boolean) | undefined;
package/dist/config.js ADDED
@@ -0,0 +1,93 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { extname, join, resolve } from 'node:path';
3
+ /** The file `ng-checkup` reads from the analysed root when no `--config` is given. */
4
+ export const CONFIG_FILE = 'ng-checkup.config.json';
5
+ /** Keys the file may contain. `$schema` is accepted so editors can attach one later. */
6
+ const KNOWN_KEYS = new Set(['$schema', 'reports', 'failOn', 'maxExamples', 'offline', 'ignore']);
7
+ /**
8
+ * Reads and validates the config file.
9
+ *
10
+ * @param explicitPath A path given with `--config`. It must exist; the default
11
+ * file may be absent, which simply means no config.
12
+ * @returns The parsed config and the directory its relative paths resolve
13
+ * against, or an empty config when there is no file.
14
+ * @throws An `Error` whose message is meant for the user on malformed JSON,
15
+ * an unknown key or a value of the wrong type. A typo such as `failon`
16
+ * fails loudly instead of being silently ignored, which would leave CI
17
+ * passing on a threshold nobody set.
18
+ */
19
+ export function loadConfig(root, explicitPath) {
20
+ const path = explicitPath ? resolve(explicitPath) : join(root, CONFIG_FILE);
21
+ if (!existsSync(path)) {
22
+ if (explicitPath)
23
+ throw new Error(`Config file not found: ${path}`);
24
+ return { config: {}, directory: root };
25
+ }
26
+ let raw;
27
+ try {
28
+ raw = JSON.parse(readFileSync(path, 'utf8'));
29
+ }
30
+ catch (error) {
31
+ throw new Error(`${path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
32
+ }
33
+ if (typeof raw !== 'object' || raw === null || Array.isArray(raw))
34
+ throw new Error(`${path} must contain a JSON object`);
35
+ const record = raw;
36
+ const unknown = Object.keys(record).filter((key) => !KNOWN_KEYS.has(key));
37
+ if (unknown.length > 0)
38
+ throw new Error(`${path}: unknown ${unknown.length === 1 ? 'key' : 'keys'} ${unknown.join(', ')}. Allowed: ${[...KNOWN_KEYS].filter((key) => key !== '$schema').join(', ')}`);
39
+ const config = {};
40
+ if (record['reports'] !== undefined) {
41
+ const reports = record['reports'];
42
+ if (!Array.isArray(reports) || !reports.every((entry) => typeof entry === 'string'))
43
+ throw new Error(`${path}: reports must be an array of file names`);
44
+ for (const report of reports) {
45
+ if (!['.html', '.htm', '.md', '.json'].includes(extname(report).toLowerCase()))
46
+ throw new Error(`${path}: unknown report format for ${report}, use .html, .md or .json`);
47
+ }
48
+ config.reports = reports;
49
+ }
50
+ if (record['failOn'] !== undefined) {
51
+ if (record['failOn'] !== 'fair' && record['failOn'] !== 'poor')
52
+ throw new Error(`${path}: failOn must be "poor" or "fair"`);
53
+ config.failOn = record['failOn'];
54
+ }
55
+ if (record['maxExamples'] !== undefined) {
56
+ const value = record['maxExamples'];
57
+ if (typeof value !== 'number' || !Number.isInteger(value) || value < 0)
58
+ throw new Error(`${path}: maxExamples must be a whole number`);
59
+ config.maxExamples = value;
60
+ }
61
+ if (record['offline'] !== undefined) {
62
+ if (typeof record['offline'] !== 'boolean')
63
+ throw new Error(`${path}: offline must be true or false`);
64
+ config.offline = record['offline'];
65
+ }
66
+ if (record['ignore'] !== undefined) {
67
+ const ignore = record['ignore'];
68
+ if (!Array.isArray(ignore) || !ignore.every((entry) => typeof entry === 'string'))
69
+ throw new Error(`${path}: ignore must be an array of paths`);
70
+ config.ignore = ignore;
71
+ }
72
+ return { config, directory: resolve(path, '..'), path };
73
+ }
74
+ /**
75
+ * Compiles `ignore` patterns into one predicate over report paths (relative
76
+ * to the root, `/`-separated). Undefined when there are no patterns, so the
77
+ * file walk can skip the check entirely.
78
+ */
79
+ export function ignoreMatcher(patterns) {
80
+ const cleaned = (patterns ?? []).map((pattern) => pattern.trim().replace(/^\.\//, '').replace(/\/+$/, '')).filter(Boolean);
81
+ if (cleaned.length === 0)
82
+ return undefined;
83
+ const expressions = cleaned.map((pattern) => {
84
+ const source = pattern
85
+ .split(/(\*\*\/?|\*|\?)/)
86
+ .map((part) => (part === '**/' ? '(?:.*/)?' : part === '**' ? '.*' : part === '*' ? '[^/]*' : part === '?' ? '[^/]' : part.replace(/[.+^${}()|[\]\\]/g, '\\$&')))
87
+ .join('');
88
+ // A pattern matches the path itself or anything below it.
89
+ return new RegExp(`^${source}(?:/.*)?$`);
90
+ });
91
+ return (relativePath) => expressions.some((expression) => expression.test(relativePath));
92
+ }
93
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,30 @@
1
+ /** What a `@deprecated` tag says. */
2
+ export interface DeprecationInfo {
3
+ /** The version the tag names, such as `20.0` or `20.2`, when the tag starts with one. */
4
+ since?: string;
5
+ /** The tag's text after the version, trimmed. Can be empty: some tags give no reason. */
6
+ message: string;
7
+ }
8
+ /** The deprecated exports of one import path, such as `@angular/common` or `@angular/common/http`. */
9
+ export interface EntryDeprecations {
10
+ /** Exported name to its deprecation. */
11
+ symbols: Map<string, DeprecationInfo>;
12
+ /** `Owner.member` to its deprecation, for enum members and static class members. */
13
+ members: Map<string, DeprecationInfo>;
14
+ }
15
+ /** Import path to its deprecated exports. */
16
+ export type DeprecationIndex = Map<string, EntryDeprecations>;
17
+ /**
18
+ * Builds the deprecation index from the analysed repository's own `node_modules`.
19
+ *
20
+ * Reading the installed `.d.ts` files rather than shipping a hand-kept list is
21
+ * what keeps the report truthful: it names exactly what the installed version
22
+ * marks, with the version and reason Angular itself wrote. A hand-kept list
23
+ * drifts the moment a release adds or lifts a deprecation (Angular 22, for
24
+ * example, deprecates `ChangeDetectionStrategy.Default` in favour of `Eager`,
25
+ * because OnPush became the default).
26
+ *
27
+ * Returns an empty index when dependencies are not installed; the deprecations
28
+ * check then grades `info` and says why.
29
+ */
30
+ export declare function buildDeprecationIndex(root: string): DeprecationIndex;
@@ -0,0 +1,279 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { dirname, join, resolve } from 'node:path';
3
+ import ts from 'typescript';
4
+ import { asRecord, readJson } from './workspace.js';
5
+ /**
6
+ * Package scopes whose type definitions are indexed. Both publish their public
7
+ * API as bundled `.d.ts` files with `@deprecated` tags kept, and both are where
8
+ * an Angular upgrade breaks things.
9
+ */
10
+ const INDEXED_SCOPES = ['@angular', '@ngrx'];
11
+ /**
12
+ * Builds the deprecation index from the analysed repository's own `node_modules`.
13
+ *
14
+ * Reading the installed `.d.ts` files rather than shipping a hand-kept list is
15
+ * what keeps the report truthful: it names exactly what the installed version
16
+ * marks, with the version and reason Angular itself wrote. A hand-kept list
17
+ * drifts the moment a release adds or lifts a deprecation (Angular 22, for
18
+ * example, deprecates `ChangeDetectionStrategy.Default` in favour of `Eager`,
19
+ * because OnPush became the default).
20
+ *
21
+ * Returns an empty index when dependencies are not installed; the deprecations
22
+ * check then grades `info` and says why.
23
+ */
24
+ export function buildDeprecationIndex(root) {
25
+ const index = new Map();
26
+ for (const scope of INDEXED_SCOPES) {
27
+ const scopeDirectory = join(root, 'node_modules', scope);
28
+ if (!existsSync(scopeDirectory))
29
+ continue;
30
+ for (const packageName of safeReaddir(scopeDirectory)) {
31
+ const packageDirectory = join(scopeDirectory, packageName);
32
+ for (const [entry, typesPath] of typeEntriesOf(packageDirectory)) {
33
+ const specifier = entry === '.' ? `${scope}/${packageName}` : `${scope}/${packageName}/${entry.slice(2)}`;
34
+ const deprecations = new DtsReader().exportsOf(typesPath);
35
+ if (deprecations.symbols.size > 0 || deprecations.members.size > 0)
36
+ index.set(specifier, deprecations);
37
+ }
38
+ }
39
+ }
40
+ return index;
41
+ }
42
+ /**
43
+ * Each public entry point of a package with its type definition file.
44
+ *
45
+ * Reads `exports` (Angular 13+ and every NgRx release that matters), skipping
46
+ * wildcard and non-code entries. A package without an `exports` map uses the
47
+ * older layout, where every secondary entry point (`@angular/core/testing`,
48
+ * `@angular/common/http`) is a folder with its own `package.json` naming its
49
+ * `typings`; those are found by `legacyEntriesOf`.
50
+ */
51
+ function typeEntriesOf(packageDirectory) {
52
+ const packageJson = readJson(join(packageDirectory, 'package.json'));
53
+ if (!packageJson)
54
+ return [];
55
+ const entries = [];
56
+ for (const [entry, target] of Object.entries(asRecord(packageJson['exports']))) {
57
+ if (entry.includes('*') || entry === './package.json' || !entry.startsWith('.'))
58
+ continue;
59
+ const types = asRecord(target)['types'] ?? asRecord(target)['typings'];
60
+ if (typeof types === 'string' && types.endsWith('.d.ts'))
61
+ entries.push([entry, resolve(packageDirectory, types)]);
62
+ }
63
+ if (entries.length === 0)
64
+ entries.push(...legacyEntriesOf(packageDirectory, '.', 0));
65
+ return entries.filter(([, path]) => existsSync(path));
66
+ }
67
+ /**
68
+ * Folders that never hold an entry point in the pre-`exports` layout: build
69
+ * formats, schematics and nested dependencies. Skipping them keeps the walk to
70
+ * a few dozen directories per package.
71
+ */
72
+ const NON_ENTRY_DIRECTORIES = new Set(['node_modules', 'schematics', 'bundles', 'esm5', 'esm2015', 'esm2020', 'fesm5', 'fesm2015', 'fesm2020', 'src', 'locales', 'third_party', 'migrations']);
73
+ /**
74
+ * Entry points of a package in the pre-`exports` layout (Angular 12 and
75
+ * older): this folder's `package.json` `typings`, then each subfolder's, up to
76
+ * three levels deep (`@angular/common/http/testing` is the deepest Angular has).
77
+ *
78
+ * @param entry The entry as an `exports` key would spell it: `.`, `./testing`.
79
+ */
80
+ function legacyEntriesOf(directory, entry, depth) {
81
+ const entries = [];
82
+ const packageJson = readJson(join(directory, 'package.json'));
83
+ const types = packageJson?.['types'] ?? packageJson?.['typings'];
84
+ if (typeof types === 'string')
85
+ entries.push([entry, resolve(directory, types)]);
86
+ if (depth >= 3)
87
+ return entries;
88
+ for (const name of safeReaddir(directory)) {
89
+ if (NON_ENTRY_DIRECTORIES.has(name) || name.startsWith('.'))
90
+ continue;
91
+ const child = join(directory, name);
92
+ if (!existsSync(join(child, 'package.json')))
93
+ continue;
94
+ entries.push(...legacyEntriesOf(child, `${entry}/${name}`, depth + 1));
95
+ }
96
+ return entries;
97
+ }
98
+ /**
99
+ * Reads deprecations out of a bundle of `.d.ts` files, following re-exports
100
+ * between them.
101
+ *
102
+ * Angular 20+ ships each entry point as a thin `.d.ts` that re-exports from
103
+ * shared chunks, often under a different name (`export { a as NgIf } from
104
+ * './_common_module-chunk.js'`), so the deprecation sits on a declaration in
105
+ * one file and the public name lives in another. One reader instance memoises
106
+ * per file, which matters because many entry points share the same chunks.
107
+ */
108
+ class DtsReader {
109
+ cache = new Map();
110
+ /** The deprecated names `path` exports, under the names it exports them as. */
111
+ exportsOf(path, depth = 0) {
112
+ const cached = this.cache.get(path);
113
+ if (cached)
114
+ return cached;
115
+ const result = { symbols: new Map(), members: new Map() };
116
+ // Set before recursing, so a re-export cycle between chunks ends here instead of overflowing.
117
+ this.cache.set(path, result);
118
+ if (depth > 8 || !existsSync(path))
119
+ return result;
120
+ const source = ts.createSourceFile(path, readFileSync(path, 'utf8'), ts.ScriptTarget.Latest, true);
121
+ const local = localDeprecations(source);
122
+ /** Names imported from sibling chunks, which a later `export { x }` may re-export. */
123
+ const importedFrom = new Map();
124
+ const exportAs = (from, original, exported) => {
125
+ const symbol = from.symbols.get(original);
126
+ if (symbol)
127
+ result.symbols.set(exported, symbol);
128
+ for (const [member, info] of from.members) {
129
+ if (member.startsWith(`${original}.`))
130
+ result.members.set(`${exported}${member.slice(original.length)}`, info);
131
+ }
132
+ };
133
+ for (const statement of source.statements) {
134
+ if (ts.isImportDeclaration(statement) && ts.isStringLiteralLike(statement.moduleSpecifier) && statement.moduleSpecifier.text.startsWith('.')) {
135
+ const file = siblingDts(path, statement.moduleSpecifier.text);
136
+ const named = statement.importClause?.namedBindings;
137
+ if (named && ts.isNamedImports(named)) {
138
+ for (const element of named.elements)
139
+ importedFrom.set(element.name.text, { file, name: (element.propertyName ?? element.name).text });
140
+ }
141
+ continue;
142
+ }
143
+ if (ts.isExportDeclaration(statement)) {
144
+ const moduleSpecifier = statement.moduleSpecifier;
145
+ const target = moduleSpecifier && ts.isStringLiteralLike(moduleSpecifier) && moduleSpecifier.text.startsWith('.') ? this.exportsOf(siblingDts(path, moduleSpecifier.text), depth + 1) : undefined;
146
+ const clause = statement.exportClause;
147
+ if (!clause) {
148
+ // `export * from './x.js'`: everything x exports, under the same names.
149
+ if (target) {
150
+ for (const [name, info] of target.symbols)
151
+ result.symbols.set(name, info);
152
+ for (const [name, info] of target.members)
153
+ result.members.set(name, info);
154
+ }
155
+ continue;
156
+ }
157
+ if (!ts.isNamedExports(clause))
158
+ continue;
159
+ for (const element of clause.elements) {
160
+ const original = (element.propertyName ?? element.name).text;
161
+ const exported = element.name.text;
162
+ if (target) {
163
+ exportAs(target, original, exported);
164
+ }
165
+ else if (local.symbols.has(original) || [...local.members.keys()].some((key) => key.startsWith(`${original}.`))) {
166
+ exportAs(local, original, exported);
167
+ }
168
+ else {
169
+ const imported = importedFrom.get(original);
170
+ if (imported)
171
+ exportAs(this.exportsOf(imported.file, depth + 1), imported.name, exported);
172
+ }
173
+ }
174
+ continue;
175
+ }
176
+ // `export declare class X` and friends, exported under their own name.
177
+ if (ts.canHaveModifiers(statement) && ts.getModifiers(statement)?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword)) {
178
+ for (const name of declaredNames(statement))
179
+ exportAs(local, name, name);
180
+ }
181
+ }
182
+ return result;
183
+ }
184
+ }
185
+ /**
186
+ * Deprecated top-level declarations of one file, by their local name, plus
187
+ * deprecated enum members and static class members.
188
+ *
189
+ * A name counts as deprecated only when **every** declaration of it carries the
190
+ * tag. Overloaded functions often deprecate one signature (an old parameter
191
+ * shape) while the function itself is fine, and an interface merged with a
192
+ * const (how Angular declares decorators) can deprecate one half. Flagging those
193
+ * would accuse every caller of using a deprecated API.
194
+ */
195
+ function localDeprecations(source) {
196
+ const declarations = new Map();
197
+ const members = new Map();
198
+ for (const statement of source.statements) {
199
+ const info = deprecationOf(statement);
200
+ for (const name of declaredNames(statement)) {
201
+ const list = declarations.get(name) ?? [];
202
+ list.push(info);
203
+ declarations.set(name, list);
204
+ }
205
+ if (ts.isEnumDeclaration(statement)) {
206
+ for (const member of statement.members) {
207
+ const memberInfo = deprecationOf(member);
208
+ if (memberInfo && ts.isIdentifier(member.name))
209
+ members.set(`${statement.name.text}.${member.name.text}`, memberInfo);
210
+ }
211
+ }
212
+ if (ts.isClassDeclaration(statement) && statement.name) {
213
+ const byMember = new Map();
214
+ for (const member of statement.members) {
215
+ const isStatic = ts.canHaveModifiers(member) && ts.getModifiers(member)?.some((modifier) => modifier.kind === ts.SyntaxKind.StaticKeyword);
216
+ if (!isStatic || !member.name || !ts.isIdentifier(member.name))
217
+ continue;
218
+ const list = byMember.get(member.name.text) ?? [];
219
+ list.push(deprecationOf(member));
220
+ byMember.set(member.name.text, list);
221
+ }
222
+ for (const [name, list] of byMember) {
223
+ const first = list[0];
224
+ if (first && list.every(Boolean))
225
+ members.set(`${statement.name.text}.${name}`, first);
226
+ }
227
+ }
228
+ }
229
+ const symbols = new Map();
230
+ for (const [name, list] of declarations) {
231
+ // `ɵ` marks Angular's private API. Its deprecations are internal bookkeeping, not upgrade advice.
232
+ if (name.startsWith('ɵ'))
233
+ continue;
234
+ const first = list[0];
235
+ if (first && list.every(Boolean))
236
+ symbols.set(name, first);
237
+ }
238
+ return { symbols, members };
239
+ }
240
+ /** The names a top-level statement declares. Empty for anything that declares none. */
241
+ function declaredNames(statement) {
242
+ if (ts.isVariableStatement(statement)) {
243
+ return statement.declarationList.declarations.flatMap((declaration) => (ts.isIdentifier(declaration.name) ? [declaration.name.text] : []));
244
+ }
245
+ if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement) || ts.isInterfaceDeclaration(statement) || ts.isTypeAliasDeclaration(statement) || ts.isEnumDeclaration(statement)) &&
246
+ statement.name) {
247
+ return [statement.name.text];
248
+ }
249
+ return [];
250
+ }
251
+ /** The node's `@deprecated` tag, parsed, or undefined when it has none. */
252
+ function deprecationOf(node) {
253
+ const tag = ts.getJSDocDeprecatedTag(node);
254
+ if (!tag)
255
+ return undefined;
256
+ const text = (ts.getTextOfJSDocComment(tag.comment) ?? '').replace(/\s+/g, ' ').trim();
257
+ // Angular writes the version three ways: `20.0 Use …`, `from v19.0.0, use …`, `since v17`.
258
+ const version = /^(?:(?:from|since|in)\s+)?v?(\d+(?:\.\d+){0,2})\b[:.,]?\s*/i.exec(text);
259
+ return version ? { since: version[1], message: text.slice(version[0].length) } : { since: undefined, message: text };
260
+ }
261
+ /** `./_chunk.js` next to `from`, as the `.d.ts` that types it. */
262
+ function siblingDts(from, specifier) {
263
+ const base = resolve(dirname(from), specifier);
264
+ if (base.endsWith('.d.ts'))
265
+ return base;
266
+ if (base.endsWith('.js') || base.endsWith('.mjs'))
267
+ return base.replace(/\.m?js$/, '.d.ts');
268
+ return existsSync(`${base}.d.ts`) ? `${base}.d.ts` : join(base, 'index.d.ts');
269
+ }
270
+ /** `readdirSync` that treats an unreadable directory as empty. */
271
+ function safeReaddir(directory) {
272
+ try {
273
+ return readdirSync(directory);
274
+ }
275
+ catch {
276
+ return [];
277
+ }
278
+ }
279
+ //# sourceMappingURL=deprecations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"deprecations.js","sourceRoot":"","sources":["../src/deprecations.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /** Everything `collectFiles` found, as absolute paths. */
2
+ export interface CollectedFiles {
3
+ /** `.ts` files, without `.d.ts` declaration files. */
4
+ typescript: string[];
5
+ /** `.html` files, which in an Angular repository are almost all component templates. */
6
+ html: string[];
7
+ /** Every `project.json`, which is how an Nx workspace describes its projects. */
8
+ projectJsons: string[];
9
+ }
10
+ /**
11
+ * Walks `root` once and sorts the files an analysis needs by kind.
12
+ *
13
+ * One walk serves the whole run, so a large monorepo is read from disk once.
14
+ * The result is sorted, which keeps report output stable between runs on the
15
+ * same tree. An unreadable directory is skipped rather than failing the run,
16
+ * since a permissions problem in one corner says nothing about the code.
17
+ *
18
+ * @param isIgnored The config file's `ignore` patterns as a predicate over
19
+ * report paths. An ignored directory is not descended into at all, which is
20
+ * what makes ignoring a large generated folder cheap.
21
+ */
22
+ export declare function collectFiles(root: string, isIgnored?: (relativePath: string) => boolean): CollectedFiles;
23
+ /**
24
+ * The form every report uses for a path: relative to the analysed root, with
25
+ * `/` separators on every platform, so a report generated on Windows reads the
26
+ * same as one generated on macOS.
27
+ */
28
+ export declare function toReportPath(root: string, absolute: string): string;
29
+ /**
30
+ * True for unit-test files. Angular CLI and Nx both name them `*.spec.ts`;
31
+ * `*.test.ts` covers Jest and Vitest setups that follow the other convention.
32
+ */
33
+ export declare function isSpecFile(path: string): boolean;
package/dist/files.js ADDED
@@ -0,0 +1,98 @@
1
+ import { readdirSync, statSync } from 'node:fs';
2
+ import { join, relative, sep } from 'node:path';
3
+ /**
4
+ * Directory names never descended into.
5
+ *
6
+ * Build output, caches and dependencies hold generated code that would swamp
7
+ * every count (one `dist` folder can double the `any` total), and none of it is
8
+ * code the team maintains. Hidden directories are skipped by a separate rule in
9
+ * `walk`, which is what keeps `.git`, `.angular`, `.nx` and editor folders out.
10
+ */
11
+ const SKIPPED_DIRECTORIES = new Set(['node_modules', 'dist', 'coverage', 'out-tsc', 'tmp', 'bazel-out']);
12
+ /**
13
+ * Files above this size are skipped as generated. A hand-written Angular source
14
+ * file is far below it, while a checked-in API client or a bundled vendor file
15
+ * is not, and parsing one can cost more than the rest of the repository.
16
+ */
17
+ const MAX_FILE_BYTES = 1_000_000;
18
+ /**
19
+ * Walks `root` once and sorts the files an analysis needs by kind.
20
+ *
21
+ * One walk serves the whole run, so a large monorepo is read from disk once.
22
+ * The result is sorted, which keeps report output stable between runs on the
23
+ * same tree. An unreadable directory is skipped rather than failing the run,
24
+ * since a permissions problem in one corner says nothing about the code.
25
+ *
26
+ * @param isIgnored The config file's `ignore` patterns as a predicate over
27
+ * report paths. An ignored directory is not descended into at all, which is
28
+ * what makes ignoring a large generated folder cheap.
29
+ */
30
+ export function collectFiles(root, isIgnored) {
31
+ const result = { typescript: [], html: [], projectJsons: [] };
32
+ walk(root, root, result, isIgnored);
33
+ result.typescript.sort();
34
+ result.html.sort();
35
+ result.projectJsons.sort();
36
+ return result;
37
+ }
38
+ /**
39
+ * The recursive half of `collectFiles`. Mutates `into`.
40
+ */
41
+ function walk(root, directory, into, isIgnored) {
42
+ let entries;
43
+ try {
44
+ entries = readdirSync(directory, { withFileTypes: true });
45
+ }
46
+ catch {
47
+ return;
48
+ }
49
+ for (const entry of entries) {
50
+ const path = join(directory, entry.name);
51
+ if (isIgnored?.(toReportPath(root, path)))
52
+ continue;
53
+ if (entry.isDirectory()) {
54
+ if (entry.name.startsWith('.') || SKIPPED_DIRECTORIES.has(entry.name))
55
+ continue;
56
+ walk(root, path, into, isIgnored);
57
+ continue;
58
+ }
59
+ if (!entry.isFile())
60
+ continue;
61
+ if (entry.name === 'project.json') {
62
+ into.projectJsons.push(path);
63
+ }
64
+ else if (entry.name.endsWith('.ts') && !entry.name.endsWith('.d.ts')) {
65
+ if (isWithinSizeLimit(path))
66
+ into.typescript.push(path);
67
+ }
68
+ else if (entry.name.endsWith('.html')) {
69
+ if (isWithinSizeLimit(path))
70
+ into.html.push(path);
71
+ }
72
+ }
73
+ }
74
+ /** False for files `MAX_FILE_BYTES` treats as generated, and for files that vanished mid-walk. */
75
+ function isWithinSizeLimit(path) {
76
+ try {
77
+ return statSync(path).size <= MAX_FILE_BYTES;
78
+ }
79
+ catch {
80
+ return false;
81
+ }
82
+ }
83
+ /**
84
+ * The form every report uses for a path: relative to the analysed root, with
85
+ * `/` separators on every platform, so a report generated on Windows reads the
86
+ * same as one generated on macOS.
87
+ */
88
+ export function toReportPath(root, absolute) {
89
+ return relative(root, absolute).split(sep).join('/');
90
+ }
91
+ /**
92
+ * True for unit-test files. Angular CLI and Nx both name them `*.spec.ts`;
93
+ * `*.test.ts` covers Jest and Vitest setups that follow the other convention.
94
+ */
95
+ export function isSpecFile(path) {
96
+ return /\.(spec|test)\.ts$/.test(path);
97
+ }
98
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../src/files.ts"],"names":[],"mappings":"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"}