Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 0a4ef30

Browse files
committed
feat(@angular-devkit/build-angular): karma-coverage w/ app builder
1 parent 422e847 commit 0a4ef30

File tree

13 files changed

+127
-26
lines changed

13 files changed

+127
-26
lines changed

‎packages/angular/build/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"esbuild": "0.24.0",
3232
"fast-glob": "3.3.2",
3333
"https-proxy-agent": "7.0.5",
34+
"istanbul-lib-instrument": "6.0.3",
3435
"listr2": "8.2.4",
3536
"lmdb": "3.1.3",
3637
"magic-string": "0.30.11",

‎packages/angular/build/src/builders/application/options.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,13 @@ interface InternalOptions {
9696
* styles.
9797
*/
9898
externalRuntimeStyles?: boolean;
99+
100+
/**
101+
* Enables instrumentation to collect code coverage data for specific files.
102+
*
103+
* Used exclusively for tests and shouldn't be used for other kinds of builds.
104+
*/
105+
instrumentForCoverage?: (filename: string) => boolean;
99106
}
100107

101108
/** Full set of options for `application` builder. */
@@ -382,6 +389,7 @@ export async function normalizeOptions(
382389
define,
383390
partialSSRBuild = false,
384391
externalRuntimeStyles,
392+
instrumentForCoverage,
385393
} = options;
386394

387395
// Return all the normalized options
@@ -444,6 +452,7 @@ export async function normalizeOptions(
444452
define,
445453
partialSSRBuild: usePartialSsrBuild || partialSSRBuild,
446454
externalRuntimeStyles,
455+
instrumentForCoverage,
447456
};
448457
}
449458

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
import { NodePath, PluginObj, types } from '@babel/core';
10+
import { Visitor, programVisitor } from 'istanbul-lib-instrument';
11+
import assert from 'node:assert';
12+
13+
/**
14+
* A babel plugin factory function for adding istanbul instrumentation.
15+
*
16+
* @returns A babel plugin object instance.
17+
*/
18+
export default function (): PluginObj {
19+
const visitors = new WeakMap<NodePath, Visitor>();
20+
21+
return {
22+
visitor: {
23+
Program: {
24+
enter(path, state) {
25+
const visitor = programVisitor(types, state.filename, {
26+
// Babel returns a Converter object from the `convert-source-map` package
27+
inputSourceMap: (state.file.inputMap as undefined | { toObject(): object })?.toObject(),
28+
});
29+
visitors.set(path, visitor);
30+
31+
visitor.enter(path);
32+
},
33+
exit(path) {
34+
const visitor = visitors.get(path);
35+
assert(visitor, 'Instrumentation visitor should always be present for program path.');
36+
37+
visitor.exit(path);
38+
visitors.delete(path);
39+
},
40+
},
41+
},
42+
};
43+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @license
3+
* Copyright Google LLC All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.dev/license
7+
*/
8+
9+
declare module 'istanbul-lib-instrument' {
10+
export interface Visitor {
11+
enter(path: import('@babel/core').NodePath<types.Program>): void;
12+
exit(path: import('@babel/core').NodePath<types.Program>): void;
13+
}
14+
15+
export function programVisitor(
16+
types: typeof import('@babel/core').types,
17+
filePath?: string,
18+
options?: { inputSourceMap?: object | null },
19+
): Visitor;
20+
}

‎packages/angular/build/src/tools/esbuild/angular/compiler-plugin.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ export interface CompilerPluginOptions {
5050
loadResultCache?: LoadResultCache;
5151
incremental: boolean;
5252
externalRuntimeStyles?: boolean;
53+
instrumentForCoverage?: (request: string) => boolean;
5354
}
5455

5556
// eslint-disable-next-line max-lines-per-function
@@ -441,11 +442,13 @@ export function createCompilerPlugin(
441442
// A string indicates untransformed output from the TS/NG compiler.
442443
// This step is unneeded when using esbuild transpilation.
443444
const sideEffects = await hasSideEffects(request);
445+
const instrumentForCoverage = pluginOptions.instrumentForCoverage?.(request);
444446
contents = await javascriptTransformer.transformData(
445447
request,
446448
contents,
447449
true /* skipLinker */,
448450
sideEffects,
451+
instrumentForCoverage,
449452
);
450453

451454
// Store as the returned Uint8Array to allow caching the fully transformed code

‎packages/angular/build/src/tools/esbuild/compiler-plugin-options.ts‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export function createCompilerPluginOptions(
3838
postcssConfiguration,
3939
publicPath,
4040
externalRuntimeStyles,
41+
instrumentForCoverage,
4142
} = options;
4243

4344
return {
@@ -53,6 +54,7 @@ export function createCompilerPluginOptions(
5354
loadResultCache: sourceFileCache?.loadResultCache,
5455
incremental: !!options.watch,
5556
externalRuntimeStyles,
57+
instrumentForCoverage,
5658
},
5759
// Component stylesheet options
5860
styleOptions: {

‎packages/angular/build/src/tools/esbuild/javascript-transformer-worker.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ interface JavaScriptTransformRequest {
2121
skipLinker?: boolean;
2222
sideEffects?: boolean;
2323
jit: boolean;
24+
instrumentForCoverage?: boolean;
2425
}
2526

2627
const textDecoder = new TextDecoder();
@@ -64,8 +65,13 @@ async function transformWithBabel(
6465
const { default: importAttributePlugin } = await import('@babel/plugin-syntax-import-attributes');
6566
const plugins: PluginItem[] = [importAttributePlugin];
6667

67-
// Lazy load the linker plugin only when linking is required
68+
if (options.instrumentForCoverage) {
69+
const { default: coveragePlugin } = await import('../babel/plugins/add-code-coverage.js');
70+
plugins.push(coveragePlugin);
71+
}
72+
6873
if (shouldLink) {
74+
// Lazy load the linker plugin only when linking is required
6975
const linkerPlugin = await createLinkerPlugin(options);
7076
plugins.push(linkerPlugin);
7177
}

‎packages/angular/build/src/tools/esbuild/javascript-transformer.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export class JavaScriptTransformer {
7575
filename: string,
7676
skipLinker?: boolean,
7777
sideEffects?: boolean,
78+
instrumentForCoverage?: boolean,
7879
): Promise<Uint8Array> {
7980
const data = await readFile(filename);
8081

@@ -105,6 +106,7 @@ export class JavaScriptTransformer {
105106
data,
106107
skipLinker,
107108
sideEffects,
109+
instrumentForCoverage,
108110
...this.#commonOptions,
109111
},
110112
{
@@ -141,10 +143,11 @@ export class JavaScriptTransformer {
141143
data: string,
142144
skipLinker: boolean,
143145
sideEffects?: boolean,
146+
instrumentForCoverage?: boolean,
144147
): Promise<Uint8Array> {
145148
// Perform a quick test to determine if the data needs any transformations.
146149
// This allows directly returning the data without the worker communication overhead.
147-
if (skipLinker && !this.#commonOptions.advancedOptimizations) {
150+
if (skipLinker && !this.#commonOptions.advancedOptimizations&&!instrumentForCoverage) {
148151
const keepSourcemap =
149152
this.#commonOptions.sourcemap &&
150153
(!!this.#commonOptions.thirdPartySourcemaps || !/[\\/]node_modules[\\/]/.test(filename));
@@ -160,6 +163,7 @@ export class JavaScriptTransformer {
160163
data,
161164
skipLinker,
162165
sideEffects,
166+
instrumentForCoverage,
163167
...this.#commonOptions,
164168
});
165169
}

‎packages/angular_devkit/build_angular/src/builders/karma/application_builder.ts‎

Lines changed: 33 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from '@angular/build/private';
1616
import { BuilderContext, BuilderOutput } from '@angular-devkit/architect';
1717
import { randomUUID } from 'crypto';
18+
import glob from 'fast-glob';
1819
import * as fs from 'fs/promises';
1920
import type { Config, ConfigOptions, InlinePluginDef } from 'karma';
2021
import * as path from 'path';
@@ -87,9 +88,8 @@ async function getProjectSourceRoot(context: BuilderContext): Promise<string> {
8788
async function collectEntrypoints(
8889
options: KarmaBuilderOptions,
8990
context: BuilderContext,
91+
projectSourceRoot: string,
9092
): Promise<[Set<string>, string[]]> {
91-
const projectSourceRoot = await getProjectSourceRoot(context);
92-
9393
// Glob for files to test.
9494
const testFiles = await findTests(
9595
options.include ?? [],
@@ -127,15 +127,23 @@ async function initializeApplication(
127127
}
128128

129129
const testDir = path.join(context.workspaceRoot, 'dist/test-out', randomUUID());
130+
const projectSourceRoot = await getProjectSourceRoot(context);
130131

131132
const [karma, [entryPoints, polyfills]] = await Promise.all([
132133
import('karma'),
133-
collectEntrypoints(options, context),
134+
collectEntrypoints(options, context,projectSourceRoot),
134135
fs.rm(testDir, { recursive: true, force: true }),
135136
]);
136137

137138
const outputPath = testDir;
138139

140+
const instrumentForCoverage = options.codeCoverage
141+
? createInstrumentationFilter(
142+
projectSourceRoot,
143+
getInstrumentationExcludedPaths(context.workspaceRoot, options.codeCoverageExclude ?? []),
144+
)
145+
: undefined;
146+
139147
// Build tests with `application` builder, using test files as entry points.
140148
const buildOutput = await first(
141149
buildApplicationInternal(
@@ -152,6 +160,7 @@ async function initializeApplication(
152160
styles: true,
153161
vendor: true,
154162
},
163+
instrumentForCoverage,
155164
styles: options.styles,
156165
polyfills,
157166
webWorkerTsConfig: options.webWorkerTsConfig,
@@ -281,3 +290,24 @@ async function first<T>(generator: AsyncIterable<T>): Promise<T> {
281290

282291
throw new Error('Expected generator to emit at least once.');
283292
}
293+
294+
function createInstrumentationFilter(includedBasePath: string, excludedPaths: Set<string>) {
295+
return (request: string): boolean => {
296+
return (
297+
!excludedPaths.has(request) &&
298+
!/\.(e2e|spec)\.tsx?$|[\\/]node_modules[\\/]/.test(request) &&
299+
request.startsWith(includedBasePath)
300+
);
301+
};
302+
}
303+
304+
function getInstrumentationExcludedPaths(root: string, excludedPaths: string[]): Set<string> {
305+
const excluded = new Set<string>();
306+
307+
for (const excludeGlob of excludedPaths) {
308+
const excludePath = excludeGlob[0] === '/' ? excludeGlob.slice(1) : excludeGlob;
309+
glob.sync(excludePath, { cwd: root }).forEach((p) => excluded.add(path.join(root, p)));
310+
}
311+
312+
return excluded;
313+
}

‎packages/angular_devkit/build_angular/src/builders/karma/tests/behavior/code-coverage_spec.ts‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,8 @@ import { BASE_OPTIONS, KARMA_BUILDER_INFO, describeKarmaBuilder } from '../setup
2121

2222
const coveragePath = 'coverage/lcov.info';
2323

24-
describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget,isApplicationBuilder) => {
24+
describeKarmaBuilder(execute, KARMA_BUILDER_INFO, (harness, setupTarget) => {
2525
describe('Behavior: "codeCoverage"', () => {
26-
if (isApplicationBuilder) {
27-
beforeEach(() => {
28-
pending('Code coverage not implemented yet for application builder');
29-
});
30-
}
31-
3226
beforeEach(async () => {
3327
await setupTarget(harness);
3428
});

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /