diff options
Diffstat (limited to 'vanilla/node_modules/ast-v8-to-istanbul')
| -rw-r--r-- | vanilla/node_modules/ast-v8-to-istanbul/LICENSE | 21 | ||||
| -rw-r--r-- | vanilla/node_modules/ast-v8-to-istanbul/README.md | 278 | ||||
| -rw-r--r-- | vanilla/node_modules/ast-v8-to-istanbul/dist/index.d.mts | 568 | ||||
| -rw-r--r-- | vanilla/node_modules/ast-v8-to-istanbul/dist/index.mjs | 848 | ||||
| -rw-r--r-- | vanilla/node_modules/ast-v8-to-istanbul/package.json | 71 |
5 files changed, 1786 insertions, 0 deletions
diff --git a/vanilla/node_modules/ast-v8-to-istanbul/LICENSE b/vanilla/node_modules/ast-v8-to-istanbul/LICENSE new file mode 100644 index 0000000..073bd2a --- /dev/null +++ b/vanilla/node_modules/ast-v8-to-istanbul/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ari Perkkiö + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vanilla/node_modules/ast-v8-to-istanbul/README.md b/vanilla/node_modules/ast-v8-to-istanbul/README.md new file mode 100644 index 0000000..e4aa256 --- /dev/null +++ b/vanilla/node_modules/ast-v8-to-istanbul/README.md @@ -0,0 +1,278 @@ +# `ast-v8-to-istanbul` + +[![Version][version-badge]][npm-url] +[![Downloads][downloads-url]][npm-url] + +> - Speed of V8 coverage 🏎 +> - Accuracy of Istanbul coverage 🔍 + +[Ignoring code](#ignoring-code) | [Source maps](#source-maps) | [Istanbul Compatibility](#istanbul-compatibility) | [Limitations](#limitations) + +--- + +AST-aware [`v8-to-istanbul`](https://www.npmjs.com/package/v8-to-istanbul). + +Unopinionated - _bring-your-own_ AST parser and source maps. + +Passes all 195 tests<sup>[*](#istanbul-compatibility)</sup> of [`istanbul-lib-instrument`](https://github.com/istanbuljs/istanbuljs/tree/main/packages/istanbul-lib-instrument/test/specs). ✅ + +Test cases run against: +- `vite/parseAst` ✅ +- `acorn` ✅ +- `oxc-parser` ✅ +- `@babel/parser` ✅ + +See example report at https://ariperkkio.github.io/ast-v8-to-istanbul. + +```ts +import { convert } from "ast-v8-to-istanbul"; +import { parseAstAsync } from "vite"; +import type { CoverageMapData } from "istanbul-lib-coverage"; + +const data: CoverageMapData = await convert({ + // Bring-your-own AST parser + ast: parseAstAsync(<code>), + + // Code of the executed file (not the source file) + code: "function sum(a, b) {\n return a + b ...", + + // Execution wrapper offset + wrapperLength: 0, + + // Script coverage of the executed file + coverage: { + scriptId: "123", + url: "file:///absolute/path/to/dist/index.js", + functions: [ + { + functionName: "sum", + ranges: [{ startOffset: 223, endOffset: 261, count: 0 }], + isBlockCoverage: false, + }, + // ... etc + ], + }, + + // Source map of the executed file + sourceMap: { + version: 3, + sources: ["../sources.ts"], + sourcesContent: ["export function sum(a: number, b: number) {\n..."], + mappings: ";AAAO,SAAS,...", + names: [], + }, +}); +``` + +## Ignoring code + +### Ignoring source code + +#### Ignore hints + +See live example at https://ariperkkio.github.io/ast-v8-to-istanbul/ignore-examples.html. + +The typical ignore hints from `nyc` are supported: https://github.com/istanbuljs/nyc?tab=readme-ov-file#parsing-hints-ignoring-lines: + +> * `/* istanbul ignore if */`: ignore the next if statement. +> * `/* istanbul ignore else */`: ignore the else portion of an if statement. +> * `/* istanbul ignore next */`: ignore the next _thing_ in the source-code (functions, if statements, classes, you name it). +> * `/* istanbul ignore file */`: ignore an entire source-file (this should be placed at the top of the file). + +In addition to `istanbul` keyword, you can use `v8`, `c8` and `node:coverage`: + +- `/* istanbul ignore if */` +- `/* v8 ignore else */` +- `/* c8 ignore file */` +- `/* node:coverage ignore next */` + +Also `start` and `stop` ignore hints from original [`v8-to-istanbul`](https://www.npmjs.com/package/v8-to-istanbul) are supported. +These ignore hints are checked from the original sources instead of transpiled code. + +> * `/* v8 ignore start */`: start ignoring lines +> * `/* v8 ignore stop */`: stop ignoring lines +> * `<!-- /* v8 ignore start */ -->`: start ignoring lines +> * `<!-- /* v8 ignore stop */ -->`: stop ignoring lines +> * `anything /* v8 ignore start */ anything`: start ignoring lines +> * `anything /* v8 ignore stop */ anything`: stop ignoring lines + +#### Class methods + +The `ignore-class-method` from `nyc` is also supported: https://github.com/istanbuljs/nyc?tab=readme-ov-file#ignoring-methods + +> You can ignore every instance of a method simply by adding its name to the `ignore-class-method` array in your `nyc` config. + +```ts +import { convert } from "ast-v8-to-istanbul"; + +await convert({ + ignoreClassMethods: ['render'] +}); +``` + +#### Ignore after remapping + +You can ignore source code after coverage results have been remapped back to original sources using `ignoreSourceCode`. +This is a high level API that can be exposed to end-users by tooling developers. + +It's mostly intended for excluding code that is incorrectly shown in coverage report when compilers add generated code in the source maps. + +Note that as the exclusion happens after remapping, this option is slower than [`ignoreNode`](#ignoring-generated-code) option. + +```ts +function ignoreSourceCode( + code: string, + type: "function" | "statement" | "branch", + location: Record<"start" | "end", { line: number; column: number }>, +): boolean | void; +``` + +```ts +import { convert } from "ast-v8-to-istanbul"; + +await convert({ + ignoreSourceCode: (code, type, location) => { + // Ignore all "noop()" calls + if(type === "function" && code.includes("noop(")) { + return true; + } + + // In Vue "<script>" tags generate code that is incorrectly left in source maps - exclude it + if(code === '<script>') { + return true; + } + + // Ignore anything above line 5 + return location.start.line < 5; + }, +}); +``` + +### Ignoring generated code + +This API is mostly for developers integrating `ast-v8-to-istanbul` with other tooling. + +If your code transform pipeline is adding generated code that's included in the source maps, it will be included in coverage too. +You can exclude these known patterns by defining `ignoreNode` for filtering such nodes. + +By returning `"ignore-this-and-nested-nodes"` from the handler, you can ignore all nested nodes too. +This can be useful when you need to ignore everything a certain node wraps, e.g. `IfStatement`. + +```ts +function ignoreNode( + node: Node, + type: "branch" | "function" | "statement" +): boolean | "ignore-this-and-nested-nodes" | void; +``` + +```ts +import { convert } from "ast-v8-to-istanbul"; + +await convert({ + ignoreNode: (node, type) => { + // Ignore all `await __vite_ssr_import__( ... )` calls that Vite SSR transform adds + return ( + type === "statement" && + node.type === "AwaitExpression" && + node.argument.type === "CallExpression" && + node.argument.callee.type === "Identifier" && + node.argument.callee.name === "__vite_ssr_import__" + ); + }, +}); +``` + +## Source maps + +Source maps are optional and supported by various ways: + +- Pass directly to `convert` as argument: + ```ts + import { convert } from "ast-v8-to-istanbul"; + + await convert({ + sourceMap: { + version: 3, + sources: ["../sources.ts"], + sourcesContent: ["export function sum(a: number, b: number) {\n..."], + mappings: ";AAAO,SAAS,...", + names: [], + } + }); + ``` +- Include base64 encoded inline maps in `code`: + ```ts + await convert({ + code: `\ + function hello() {} + //# sourceMappingURL=data:application/json;base64,eyJzb3VyY2VzIjpbIi9zb21lL... + ` + }); + ``` +- Include inline maps with filename in `code`: + ```ts + await convert({ + code: `\ + function hello() {} + //# sourceMappingURL=some-file-on-file-system.js.map + ` + }); + ``` +- Don't use source maps at all, and pass original source code in `code`: + ```ts + await convert({ + code: `function hello() {}`, + sourceMap: undefined, + }); + ``` + +## Istanbul Compatibility + +This project tests itself against test cases of `istanbul-lib-instrument` and verifies coverage maps are 100% identical. Some cases, like [deprecated `with()` statement](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/with) and edge cases of strict mode are skipped, as all tests are run in strict mode. + +100% istanbul compatibility guarantees that coverage reports between V8 and Istanbul can be merged together. + +<img src="https://github.com/user-attachments/assets/f74f129c-d63a-403e-8091-aefa53f6f97e" width="400" /> + +## Limitations + +The way how V8 reports runtime coverage has some limitations when compared to pre-instrumented coverage: + +- Unable to detect uncovered `AssignmentPattern`'s if line is otherwise covered + - https://github.com/nodejs/node/issues/57435 + +- Unable to detect uncovered parts when block execution stops due to function throwing: + - ```js + function first() { + throws() + + // unreachable, but incorrectly covered + return "first"; + } + + const throws = () => { throw new Error() } + + try { first(1) } catch {} + ``` + - ```json + [ + { + "ranges": [{ "startOffset": 0, "endOffset": 165, "count": 1 }], + "isBlockCoverage": true + }, + { + "functionName": "first", + "ranges": [{ "startOffset": 0, "endOffset": 92, "count": 1 }], + "isBlockCoverage": true + }, + { + "functionName": "throws", + "ranges": [{ "startOffset": 109, "endOffset": 137, "count": 1 }], + "isBlockCoverage": true + } + ] + ``` + +[version-badge]: https://img.shields.io/npm/v/ast-v8-to-istanbul +[npm-url]: https://www.npmjs.com/package/ast-v8-to-istanbul +[downloads-url]: https://img.shields.io/npm/dm/ast-v8-to-istanbul diff --git a/vanilla/node_modules/ast-v8-to-istanbul/dist/index.d.mts b/vanilla/node_modules/ast-v8-to-istanbul/dist/index.d.mts new file mode 100644 index 0000000..9d69ea5 --- /dev/null +++ b/vanilla/node_modules/ast-v8-to-istanbul/dist/index.d.mts @@ -0,0 +1,568 @@ +import { EncodedSourceMap } from "@jridgewell/trace-mapping"; +import { CoverageMapData } from "istanbul-lib-coverage"; +import { Profiler } from "node:inspector"; + +//#region node_modules/.pnpm/@types+estree@1.0.8/node_modules/@types/estree/index.d.ts +// This definition file follows a somewhat unusual format. ESTree allows +// runtime type checks based on the `type` parameter. In order to explain this +// to typescript we want to use discriminated union types: +// https://github.com/Microsoft/TypeScript/pull/9163 +// +// For ESTree this is a bit tricky because the high level interfaces like +// Node or Function are pulling double duty. We want to pass common fields down +// to the interfaces that extend them (like Identifier or +// ArrowFunctionExpression), but you can't extend a type union or enforce +// common fields on them. So we've split the high level interfaces into two +// types, a base type which passes down inherited fields, and a type union of +// all types which extend the base type. Only the type union is exported, and +// the union is how other types refer to the collection of inheriting types. +// +// This makes the definitions file here somewhat more difficult to maintain, +// but it has the notable advantage of making ESTree much easier to use as +// an end user. +interface BaseNodeWithoutComments { + // Every leaf interface that extends BaseNode must specify a type property. + // The type property should be a string literal. For example, Identifier + // has: `type: "Identifier"` + type: string; + loc?: SourceLocation | null | undefined; + range?: [number, number] | undefined; +} +interface BaseNode extends BaseNodeWithoutComments { + leadingComments?: Comment[] | undefined; + trailingComments?: Comment[] | undefined; +} +interface NodeMap { + AssignmentProperty: AssignmentProperty; + CatchClause: CatchClause; + Class: Class; + ClassBody: ClassBody; + Expression: Expression; + Function: Function; + Identifier: Identifier; + Literal: Literal; + MethodDefinition: MethodDefinition; + ModuleDeclaration: ModuleDeclaration; + ModuleSpecifier: ModuleSpecifier; + Pattern: Pattern; + PrivateIdentifier: PrivateIdentifier; + Program: Program; + Property: Property; + PropertyDefinition: PropertyDefinition; + SpreadElement: SpreadElement; + Statement: Statement; + Super: Super; + SwitchCase: SwitchCase; + TemplateElement: TemplateElement; + VariableDeclarator: VariableDeclarator; +} +type Node = NodeMap[keyof NodeMap]; +interface Comment extends BaseNodeWithoutComments { + type: "Line" | "Block"; + value: string; +} +interface SourceLocation { + source?: string | null | undefined; + start: Position; + end: Position; +} +interface Position { + /** >= 1 */ + line: number; + /** >= 0 */ + column: number; +} +interface Program extends BaseNode { + type: "Program"; + sourceType: "script" | "module"; + body: Array<Directive | Statement | ModuleDeclaration>; + comments?: Comment[] | undefined; +} +interface Directive extends BaseNode { + type: "ExpressionStatement"; + expression: Literal; + directive: string; +} +interface BaseFunction extends BaseNode { + params: Pattern[]; + generator?: boolean | undefined; + async?: boolean | undefined; // The body is either BlockStatement or Expression because arrow functions + // can have a body that's either. FunctionDeclarations and + // FunctionExpressions have only BlockStatement bodies. + body: BlockStatement | Expression; +} +type Function = FunctionDeclaration | FunctionExpression | ArrowFunctionExpression; +type Statement = ExpressionStatement | BlockStatement | StaticBlock | EmptyStatement | DebuggerStatement | WithStatement | ReturnStatement | LabeledStatement | BreakStatement | ContinueStatement | IfStatement | SwitchStatement | ThrowStatement | TryStatement | WhileStatement | DoWhileStatement | ForStatement | ForInStatement | ForOfStatement | Declaration; +interface BaseStatement extends BaseNode {} +interface EmptyStatement extends BaseStatement { + type: "EmptyStatement"; +} +interface BlockStatement extends BaseStatement { + type: "BlockStatement"; + body: Statement[]; + innerComments?: Comment[] | undefined; +} +interface StaticBlock extends Omit<BlockStatement, "type"> { + type: "StaticBlock"; +} +interface ExpressionStatement extends BaseStatement { + type: "ExpressionStatement"; + expression: Expression; +} +interface IfStatement extends BaseStatement { + type: "IfStatement"; + test: Expression; + consequent: Statement; + alternate?: Statement | null | undefined; +} +interface LabeledStatement extends BaseStatement { + type: "LabeledStatement"; + label: Identifier; + body: Statement; +} +interface BreakStatement extends BaseStatement { + type: "BreakStatement"; + label?: Identifier | null | undefined; +} +interface ContinueStatement extends BaseStatement { + type: "ContinueStatement"; + label?: Identifier | null | undefined; +} +interface WithStatement extends BaseStatement { + type: "WithStatement"; + object: Expression; + body: Statement; +} +interface SwitchStatement extends BaseStatement { + type: "SwitchStatement"; + discriminant: Expression; + cases: SwitchCase[]; +} +interface ReturnStatement extends BaseStatement { + type: "ReturnStatement"; + argument?: Expression | null | undefined; +} +interface ThrowStatement extends BaseStatement { + type: "ThrowStatement"; + argument: Expression; +} +interface TryStatement extends BaseStatement { + type: "TryStatement"; + block: BlockStatement; + handler?: CatchClause | null | undefined; + finalizer?: BlockStatement | null | undefined; +} +interface WhileStatement extends BaseStatement { + type: "WhileStatement"; + test: Expression; + body: Statement; +} +interface DoWhileStatement extends BaseStatement { + type: "DoWhileStatement"; + body: Statement; + test: Expression; +} +interface ForStatement extends BaseStatement { + type: "ForStatement"; + init?: VariableDeclaration | Expression | null | undefined; + test?: Expression | null | undefined; + update?: Expression | null | undefined; + body: Statement; +} +interface BaseForXStatement extends BaseStatement { + left: VariableDeclaration | Pattern; + right: Expression; + body: Statement; +} +interface ForInStatement extends BaseForXStatement { + type: "ForInStatement"; +} +interface DebuggerStatement extends BaseStatement { + type: "DebuggerStatement"; +} +type Declaration = FunctionDeclaration | VariableDeclaration | ClassDeclaration; +interface BaseDeclaration extends BaseStatement {} +interface MaybeNamedFunctionDeclaration extends BaseFunction, BaseDeclaration { + type: "FunctionDeclaration"; + /** It is null when a function declaration is a part of the `export default function` statement */ + id: Identifier | null; + body: BlockStatement; +} +interface FunctionDeclaration extends MaybeNamedFunctionDeclaration { + id: Identifier; +} +interface VariableDeclaration extends BaseDeclaration { + type: "VariableDeclaration"; + declarations: VariableDeclarator[]; + kind: "var" | "let" | "const" | "using" | "await using"; +} +interface VariableDeclarator extends BaseNode { + type: "VariableDeclarator"; + id: Pattern; + init?: Expression | null | undefined; +} +interface ExpressionMap { + ArrayExpression: ArrayExpression; + ArrowFunctionExpression: ArrowFunctionExpression; + AssignmentExpression: AssignmentExpression; + AwaitExpression: AwaitExpression; + BinaryExpression: BinaryExpression; + CallExpression: CallExpression; + ChainExpression: ChainExpression; + ClassExpression: ClassExpression; + ConditionalExpression: ConditionalExpression; + FunctionExpression: FunctionExpression; + Identifier: Identifier; + ImportExpression: ImportExpression; + Literal: Literal; + LogicalExpression: LogicalExpression; + MemberExpression: MemberExpression; + MetaProperty: MetaProperty; + NewExpression: NewExpression; + ObjectExpression: ObjectExpression; + SequenceExpression: SequenceExpression; + TaggedTemplateExpression: TaggedTemplateExpression; + TemplateLiteral: TemplateLiteral; + ThisExpression: ThisExpression; + UnaryExpression: UnaryExpression; + UpdateExpression: UpdateExpression; + YieldExpression: YieldExpression; +} +type Expression = ExpressionMap[keyof ExpressionMap]; +interface BaseExpression extends BaseNode {} +type ChainElement = SimpleCallExpression | MemberExpression; +interface ChainExpression extends BaseExpression { + type: "ChainExpression"; + expression: ChainElement; +} +interface ThisExpression extends BaseExpression { + type: "ThisExpression"; +} +interface ArrayExpression extends BaseExpression { + type: "ArrayExpression"; + elements: Array<Expression | SpreadElement | null>; +} +interface ObjectExpression extends BaseExpression { + type: "ObjectExpression"; + properties: Array<Property | SpreadElement>; +} +interface PrivateIdentifier extends BaseNode { + type: "PrivateIdentifier"; + name: string; +} +interface Property extends BaseNode { + type: "Property"; + key: Expression | PrivateIdentifier; + value: Expression | Pattern; // Could be an AssignmentProperty + kind: "init" | "get" | "set"; + method: boolean; + shorthand: boolean; + computed: boolean; +} +interface PropertyDefinition extends BaseNode { + type: "PropertyDefinition"; + key: Expression | PrivateIdentifier; + value?: Expression | null | undefined; + computed: boolean; + static: boolean; +} +interface FunctionExpression extends BaseFunction, BaseExpression { + id?: Identifier | null | undefined; + type: "FunctionExpression"; + body: BlockStatement; +} +interface SequenceExpression extends BaseExpression { + type: "SequenceExpression"; + expressions: Expression[]; +} +interface UnaryExpression extends BaseExpression { + type: "UnaryExpression"; + operator: UnaryOperator; + prefix: true; + argument: Expression; +} +interface BinaryExpression extends BaseExpression { + type: "BinaryExpression"; + operator: BinaryOperator; + left: Expression | PrivateIdentifier; + right: Expression; +} +interface AssignmentExpression extends BaseExpression { + type: "AssignmentExpression"; + operator: AssignmentOperator; + left: Pattern | MemberExpression; + right: Expression; +} +interface UpdateExpression extends BaseExpression { + type: "UpdateExpression"; + operator: UpdateOperator; + argument: Expression; + prefix: boolean; +} +interface LogicalExpression extends BaseExpression { + type: "LogicalExpression"; + operator: LogicalOperator; + left: Expression; + right: Expression; +} +interface ConditionalExpression extends BaseExpression { + type: "ConditionalExpression"; + test: Expression; + alternate: Expression; + consequent: Expression; +} +interface BaseCallExpression extends BaseExpression { + callee: Expression | Super; + arguments: Array<Expression | SpreadElement>; +} +type CallExpression = SimpleCallExpression | NewExpression; +interface SimpleCallExpression extends BaseCallExpression { + type: "CallExpression"; + optional: boolean; +} +interface NewExpression extends BaseCallExpression { + type: "NewExpression"; +} +interface MemberExpression extends BaseExpression, BasePattern { + type: "MemberExpression"; + object: Expression | Super; + property: Expression | PrivateIdentifier; + computed: boolean; + optional: boolean; +} +type Pattern = Identifier | ObjectPattern | ArrayPattern | RestElement | AssignmentPattern | MemberExpression; +interface BasePattern extends BaseNode {} +interface SwitchCase extends BaseNode { + type: "SwitchCase"; + test?: Expression | null | undefined; + consequent: Statement[]; +} +interface CatchClause extends BaseNode { + type: "CatchClause"; + param: Pattern | null; + body: BlockStatement; +} +interface Identifier extends BaseNode, BaseExpression, BasePattern { + type: "Identifier"; + name: string; +} +type Literal = SimpleLiteral | RegExpLiteral | BigIntLiteral; +interface SimpleLiteral extends BaseNode, BaseExpression { + type: "Literal"; + value: string | boolean | number | null; + raw?: string | undefined; +} +interface RegExpLiteral extends BaseNode, BaseExpression { + type: "Literal"; + value?: RegExp | null | undefined; + regex: { + pattern: string; + flags: string; + }; + raw?: string | undefined; +} +interface BigIntLiteral extends BaseNode, BaseExpression { + type: "Literal"; + value?: bigint | null | undefined; + bigint: string; + raw?: string | undefined; +} +type UnaryOperator = "-" | "+" | "!" | "~" | "typeof" | "void" | "delete"; +type BinaryOperator = "==" | "!=" | "===" | "!==" | "<" | "<=" | ">" | ">=" | "<<" | ">>" | ">>>" | "+" | "-" | "*" | "/" | "%" | "**" | "|" | "^" | "&" | "in" | "instanceof"; +type LogicalOperator = "||" | "&&" | "??"; +type AssignmentOperator = "=" | "+=" | "-=" | "*=" | "/=" | "%=" | "**=" | "<<=" | ">>=" | ">>>=" | "|=" | "^=" | "&=" | "||=" | "&&=" | "??="; +type UpdateOperator = "++" | "--"; +interface ForOfStatement extends BaseForXStatement { + type: "ForOfStatement"; + await: boolean; +} +interface Super extends BaseNode { + type: "Super"; +} +interface SpreadElement extends BaseNode { + type: "SpreadElement"; + argument: Expression; +} +interface ArrowFunctionExpression extends BaseExpression, BaseFunction { + type: "ArrowFunctionExpression"; + expression: boolean; + body: BlockStatement | Expression; +} +interface YieldExpression extends BaseExpression { + type: "YieldExpression"; + argument?: Expression | null | undefined; + delegate: boolean; +} +interface TemplateLiteral extends BaseExpression { + type: "TemplateLiteral"; + quasis: TemplateElement[]; + expressions: Expression[]; +} +interface TaggedTemplateExpression extends BaseExpression { + type: "TaggedTemplateExpression"; + tag: Expression; + quasi: TemplateLiteral; +} +interface TemplateElement extends BaseNode { + type: "TemplateElement"; + tail: boolean; + value: { + /** It is null when the template literal is tagged and the text has an invalid escape (e.g. - tag`\unicode and \u{55}`) */cooked?: string | null | undefined; + raw: string; + }; +} +interface AssignmentProperty extends Property { + value: Pattern; + kind: "init"; + method: boolean; // false +} +interface ObjectPattern extends BasePattern { + type: "ObjectPattern"; + properties: Array<AssignmentProperty | RestElement>; +} +interface ArrayPattern extends BasePattern { + type: "ArrayPattern"; + elements: Array<Pattern | null>; +} +interface RestElement extends BasePattern { + type: "RestElement"; + argument: Pattern; +} +interface AssignmentPattern extends BasePattern { + type: "AssignmentPattern"; + left: Pattern; + right: Expression; +} +type Class = ClassDeclaration | ClassExpression; +interface BaseClass extends BaseNode { + superClass?: Expression | null | undefined; + body: ClassBody; +} +interface ClassBody extends BaseNode { + type: "ClassBody"; + body: Array<MethodDefinition | PropertyDefinition | StaticBlock>; +} +interface MethodDefinition extends BaseNode { + type: "MethodDefinition"; + key: Expression | PrivateIdentifier; + value: FunctionExpression; + kind: "constructor" | "method" | "get" | "set"; + computed: boolean; + static: boolean; +} +interface MaybeNamedClassDeclaration extends BaseClass, BaseDeclaration { + type: "ClassDeclaration"; + /** It is null when a class declaration is a part of the `export default class` statement */ + id: Identifier | null; +} +interface ClassDeclaration extends MaybeNamedClassDeclaration { + id: Identifier; +} +interface ClassExpression extends BaseClass, BaseExpression { + type: "ClassExpression"; + id?: Identifier | null | undefined; +} +interface MetaProperty extends BaseExpression { + type: "MetaProperty"; + meta: Identifier; + property: Identifier; +} +type ModuleDeclaration = ImportDeclaration | ExportNamedDeclaration | ExportDefaultDeclaration | ExportAllDeclaration; +interface BaseModuleDeclaration extends BaseNode {} +type ModuleSpecifier = ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier | ExportSpecifier; +interface BaseModuleSpecifier extends BaseNode { + local: Identifier; +} +interface ImportDeclaration extends BaseModuleDeclaration { + type: "ImportDeclaration"; + specifiers: Array<ImportSpecifier | ImportDefaultSpecifier | ImportNamespaceSpecifier>; + attributes: ImportAttribute[]; + source: Literal; +} +interface ImportSpecifier extends BaseModuleSpecifier { + type: "ImportSpecifier"; + imported: Identifier | Literal; +} +interface ImportAttribute extends BaseNode { + type: "ImportAttribute"; + key: Identifier | Literal; + value: Literal; +} +interface ImportExpression extends BaseExpression { + type: "ImportExpression"; + source: Expression; + options?: Expression | null | undefined; +} +interface ImportDefaultSpecifier extends BaseModuleSpecifier { + type: "ImportDefaultSpecifier"; +} +interface ImportNamespaceSpecifier extends BaseModuleSpecifier { + type: "ImportNamespaceSpecifier"; +} +interface ExportNamedDeclaration extends BaseModuleDeclaration { + type: "ExportNamedDeclaration"; + declaration?: Declaration | null | undefined; + specifiers: ExportSpecifier[]; + attributes: ImportAttribute[]; + source?: Literal | null | undefined; +} +interface ExportSpecifier extends Omit<BaseModuleSpecifier, "local"> { + type: "ExportSpecifier"; + local: Identifier | Literal; + exported: Identifier | Literal; +} +interface ExportDefaultDeclaration extends BaseModuleDeclaration { + type: "ExportDefaultDeclaration"; + declaration: MaybeNamedFunctionDeclaration | MaybeNamedClassDeclaration | Expression; +} +interface ExportAllDeclaration extends BaseModuleDeclaration { + type: "ExportAllDeclaration"; + exported: Identifier | Literal | null; + attributes: ImportAttribute[]; + source: Literal; +} +interface AwaitExpression extends BaseExpression { + type: "AwaitExpression"; + argument: Expression; +} +//#endregion +//#region src/types.d.ts +interface Options<T = Node, Program = T & { + type: "Program"; +}> { + /** Code of the executed runtime file, not the original source file */ + code: string; + /** Length of the execution wrapper, e.g. wrapper used in node:vm */ + wrapperLength?: number; + /** Source map for the current file */ + sourceMap?: Omit<EncodedSourceMap, "version"> & { + version: number; + }; + /** ScriptCoverage for the current file */ + coverage: Pick<Profiler.ScriptCoverage, "functions" | "url">; + /** AST for the transpiled file that matches the coverage results */ + ast: Program | Promise<Program>; + /** Class method names to ignore for coverage, identical to https://github.com/istanbuljs/nyc?tab=readme-ov-file#ignoring-methods */ + ignoreClassMethods?: string[]; + /** Filter to ignore code based on AST nodes */ + ignoreNode?: (node: T, type: "function" | "statement" | "branch") => boolean | "ignore-this-and-nested-nodes" | void; + /** + * Filter to ignore code based on source code + * - Note that this is slower than `ignoreNode` as exclusion happens after remapping + */ + ignoreSourceCode?: (code: string, type: "function" | "statement" | "branch", location: Record<"start" | "end", { + line: number; + column: number; + }>) => boolean | void; +} +//#endregion +//#region src/index.d.ts +/** + * Maps V8 `ScriptCoverage` to Istanbul's `CoverageMap`. + * Results are identical with `istanbul-lib-instrument`. + */ +declare function convert<T = Node, Program = T & { + type: "Program"; +}>(options: Options<T, Program>): Promise<CoverageMapData>; +//#endregion +export { Options, convert, convert as default };
\ No newline at end of file diff --git a/vanilla/node_modules/ast-v8-to-istanbul/dist/index.mjs b/vanilla/node_modules/ast-v8-to-istanbul/dist/index.mjs new file mode 100644 index 0000000..8b66bc3 --- /dev/null +++ b/vanilla/node_modules/ast-v8-to-istanbul/dist/index.mjs @@ -0,0 +1,848 @@ +import { asyncWalk } from "estree-walker"; +import { dirname, resolve } from "node:path"; +import { fileURLToPath } from "node:url"; +import { LEAST_UPPER_BOUND, TraceMap, allGeneratedPositionsFor, originalPositionFor, sourceContentFor } from "@jridgewell/trace-mapping"; +import { readFileSync } from "node:fs"; +import { readFile } from "node:fs/promises"; +import jsTokens from "js-tokens"; + +//#region src/ast.ts +function getWalker() { + let nextIgnore = false; + function onIgnore(node) { + nextIgnore = node; + } + async function walk(ast, ignoreHints, ignoreClassMethods, visitors) { + return await asyncWalk(ast, { + async enter(node) { + if (nextIgnore !== false) return; + const hint = getIgnoreHint(node); + if (hint === "next") return onIgnore(node); + if (isSkipped(node)) onIgnore(node); + switch (node.type) { + case "FunctionDeclaration": return visitors.onFunctionDeclaration(node); + case "FunctionExpression": + if (ignoreClassMethods && node.id?.name) { + if (ignoreClassMethods.includes(node.id.name)) return onIgnore(node); + } + return visitors.onFunctionExpression(node); + case "MethodDefinition": return visitors.onMethodDefinition(node); + case "Property": return visitors.onProperty(node); + case "ArrowFunctionExpression": + if (node.body?.type === "ParenthesizedExpression") node.body = node.body.expression; + return visitors.onArrowFunctionExpression(node); + case "ExpressionStatement": return visitors.onExpressionStatement(node); + case "BreakStatement": return visitors.onBreakStatement(node); + case "ContinueStatement": return visitors.onContinueStatement(node); + case "DebuggerStatement": return visitors.onDebuggerStatement(node); + case "ReturnStatement": return visitors.onReturnStatement(node); + case "ThrowStatement": return visitors.onThrowStatement(node); + case "TryStatement": return visitors.onTryStatement(node); + case "ForStatement": return visitors.onForStatement(node); + case "ForInStatement": return visitors.onForInStatement(node); + case "ForOfStatement": return visitors.onForOfStatement(node); + case "WhileStatement": return visitors.onWhileStatement(node); + case "DoWhileStatement": return visitors.onDoWhileStatement(node); + case "WithStatement": return visitors.onWithStatement(node); + case "LabeledStatement": return visitors.onLabeledStatement(node); + case "VariableDeclarator": return visitors.onVariableDeclarator(node); + case "ClassBody": { + const classBody = node; + if (ignoreClassMethods) { + for (const child of classBody.body) if (child.type === "MethodDefinition" || child.type === "ClassMethod") { + const name = child.key.type === "Identifier" && child.key.name; + if (name && ignoreClassMethods.includes(name)) setSkipped(child); + } + classBody.body = classBody.body.filter((child) => !isSkipped(child)); + } + return visitors.onClassBody(classBody); + } + case "IfStatement": { + const branches = []; + if (node.consequent.type !== "BlockStatement") node.consequent = { + type: "BlockStatement", + body: [node.consequent], + start: node.consequent.start, + end: node.consequent.end + }; + if (node.alternate && node.alternate.type !== "BlockStatement") node.alternate = { + type: "BlockStatement", + body: [node.alternate], + start: node.alternate.start, + end: node.alternate.end + }; + if (hint === "if") setSkipped(node.consequent); + else branches.push(node.consequent); + if (hint === "else" && node.alternate) setSkipped(node.alternate); + else if (hint !== "if" && hint !== "else") branches.push(node.alternate); + return visitors.onIfStatement(node, branches); + } + case "SwitchStatement": { + const cases = []; + for (const _case of node.cases) if (getIgnoreHint(_case) !== "next") cases.push(_case); + return visitors.onSwitchStatement(node, cases); + } + case "ConditionalExpression": { + const branches = []; + if (node.consequent.type === "ParenthesizedExpression") node.consequent = node.consequent.expression; + if (node.alternate.type === "ParenthesizedExpression") node.alternate = node.alternate.expression; + if (getIgnoreHint(node.consequent) === "next") setSkipped(node.consequent); + else branches.push(node.consequent); + if (getIgnoreHint(node.alternate) === "next") setSkipped(node.alternate); + else branches.push(node.alternate); + return visitors.onConditionalExpression(node, branches); + } + case "LogicalExpression": { + if (isSkipped(node)) return; + const branches = []; + function visit(child) { + if (child.type === "LogicalExpression") { + setSkipped(child); + if (getIgnoreHint(child) !== "next") { + visit(child.left); + return visit(child.right); + } + } + branches.push(child); + } + visit(node); + return visitors.onLogicalExpression(node, branches); + } + case "AssignmentPattern": return visitors.onAssignmentPattern(node); + case "ClassMethod": return visitors.onClassMethod(node); + case "ObjectMethod": return visitors.onObjectMethod(node); + } + }, + async leave(node) { + if (node === nextIgnore) nextIgnore = false; + } + }); + function getIgnoreHint(node) { + for (const hint of ignoreHints) if (hint.loc.end === node.start) return hint.type; + return null; + } + } + return { + walk, + onIgnore + }; +} +const skippedNodes = /* @__PURE__ */ new WeakSet(); +function getFunctionName(node) { + if (node.type === "Identifier") return node.name; + if ("id" in node && node.id) return getFunctionName(node.id); +} +function setSkipped(node) { + skippedNodes.add(node); +} +function isSkipped(node) { + return skippedNodes.has(node); +} + +//#endregion +//#region src/coverage-map.ts +function createCoverageMapData(filename, sourceMap) { + const data = {}; + const directory = dirname(filename); + for (const source of sourceMap.sources) { + let path = filename; + if (source) if (source.startsWith("file://")) path = fileURLToPath(source); + else path = resolve(directory, source); + data[path] = { + path, + statementMap: {}, + fnMap: {}, + branchMap: {}, + s: {}, + f: {}, + b: {}, + meta: { + lastBranch: 0, + lastFunction: 0, + lastStatement: 0, + seen: {} + } + }; + } + return data; +} +function addFunction(options) { + const fileCoverage = options.coverageMapData[options.filename]; + const meta = fileCoverage.meta; + const key = `f:${cacheKey(options.decl)}`; + let index = meta.seen[key]; + if (index == null) { + index = meta.lastFunction; + meta.lastFunction++; + meta.seen[key] = index; + fileCoverage.fnMap[index] = { + name: options.name || `(anonymous_${index})`, + decl: pickLocation(options.decl), + loc: pickLocation(options.loc), + line: options.loc.start.line + }; + } + fileCoverage.f[index] ||= 0; + fileCoverage.f[index] += options.covered || 0; +} +function addStatement(options) { + const fileCoverage = options.coverageMapData[options.filename]; + const meta = fileCoverage.meta; + const key = `s:${cacheKey(options.loc)}`; + let index = meta.seen[key]; + if (index == null) { + index = meta.lastStatement; + meta.lastStatement++; + meta.seen[key] = index; + fileCoverage.statementMap[index] = pickLocation(options.loc); + } + fileCoverage.s[index] = options.covered || 0; +} +function addBranch(options) { + const fileCoverage = options.coverageMapData[options.filename]; + const meta = fileCoverage.meta; + const key = ["b", ...options.locations.map(cacheKey)].join(":"); + let index = meta.seen[key]; + if (index == null) { + index = meta.lastBranch; + meta.lastBranch++; + meta.seen[key] = index; + fileCoverage.branchMap[index] = { + loc: pickLocation(options.loc), + type: options.type, + locations: options.locations.map((loc) => pickLocation(loc)), + line: options.loc.start.line + }; + } + if (!fileCoverage.b[index]) fileCoverage.b[index] = Array(options.locations.length).fill(0); + options.covered?.forEach((hit, i) => { + fileCoverage.b[index][i] += hit; + }); +} +function pickLocation(loc) { + return { + start: { + line: loc.start.line, + column: loc.start.column + }, + end: { + line: loc.end.line, + column: loc.end.column + } + }; +} +function cacheKey(loc) { + return `${loc.start.line}:${loc.start.column}:${loc.end.line}:${loc.end.column}`; +} + +//#endregion +//#region src/ignore-hints.ts +const IGNORE_PATTERN = /^\s*(?:istanbul|[cv]8|node:coverage)\s+ignore\s+(if|else|next|file)(?=\W|$)/; +const IGNORE_LINES_PATTERN = /\s*(?:istanbul|[cv]8|node:coverage)\s+ignore\s+(start|stop)(?=\W|$)/; +const EOL_PATTERN = /\r?\n/g; +/** +* Parse ignore hints from **Javascript** code based on AST +* - Most AST parsers don't emit comments in AST like Acorn does, so parse comments manually instead. +*/ +function getIgnoreHints(code) { + const ignoreHints = []; + const tokens = jsTokens(code); + let current = 0; + let previousTokenWasIgnoreHint = false; + for (const token of tokens) { + if (previousTokenWasIgnoreHint && token.type !== "WhiteSpace" && token.type !== "LineTerminatorSequence") { + const previous = ignoreHints.at(-1); + if (previous) previous.loc.end = current; + previousTokenWasIgnoreHint = false; + } + if (token.type === "SingleLineComment" || token.type === "MultiLineComment") { + const loc = { + start: current, + end: current + token.value.length + }; + const type = token.value.replace(/^\/\*\*/, "").replace(/^\/\*/, "").replace(/\*\*\/$/, "").replace(/\*\/$/, "").replace(/^\/\//, "").match(IGNORE_PATTERN)?.[1]; + if (type === "file") return [{ + type: "file", + loc: { + start: 0, + end: 0 + } + }]; + if (type === "if" || type === "else" || type === "next") { + ignoreHints.push({ + type, + loc + }); + previousTokenWasIgnoreHint = true; + } + } + current += token.value.length; + } + return ignoreHints; +} +/** +* Parse ignore start/stop hints from **text file** based on regular expressions +* - Does not understand what a comment is in Javascript (or JSX, Vue, Svelte) +* - Parses source code (JS, TS, Vue, Svelte, anything) based on text search by +* matching for `/* <name> ignore start *\/` pattern - not by looking for real comments +* +* ```js +* /* v8 ignore start *\/ +* <!-- /* v8 ignore start *\/ --> +* <SomeFrameworkComment content="/* v8 ignore start *\/"> +* ``` +*/ +function getIgnoredLines(text) { + if (!text) return /* @__PURE__ */ new Set(); + const ranges = []; + let lineNumber = 0; + for (const line of text.split(EOL_PATTERN)) { + lineNumber++; + const match = line.match(IGNORE_LINES_PATTERN); + if (match) { + if (match[1] === "stop") { + const previous = ranges.at(-1); + if (previous && previous.stop === Infinity) previous.stop = lineNumber; + continue; + } + ranges.push({ + start: lineNumber, + stop: Infinity + }); + } + } + const ignoredLines = /* @__PURE__ */ new Set(); + for (const range of ranges) for (let line = range.start; line <= range.stop; line++) { + ignoredLines.add(line); + if (line >= lineNumber) break; + } + return ignoredLines; +} + +//#endregion +//#region src/location.ts +const WORD_PATTERN = /(\w+|\s|[^\w\s])/g; +const INLINE_MAP_PATTERN = /#\s*sourceMappingURL=(.*)\s*$/m; +const BASE_64_PREFIX = "data:application/json;base64,"; +/** How often should offset calculations be cached */ +const CACHE_THRESHOLD = 250; +var Locator = class { + #cache = /* @__PURE__ */ new Map(); + #codeParts; + #map; + #directory; + #ignoredLines = /* @__PURE__ */ new Map(); + constructor(code, map, directory) { + this.#codeParts = code.split(""); + this.#map = map; + this.#directory = directory; + } + reset() { + this.#cache.clear(); + this.#ignoredLines.clear(); + this.#codeParts = []; + } + offsetToNeedle(offset) { + const closestThreshold = Math.floor(offset / CACHE_THRESHOLD) * CACHE_THRESHOLD; + const cacheHit = this.#cache.get(closestThreshold); + let current = cacheHit ? closestThreshold : 0; + let line = cacheHit?.line ?? 1; + let column = cacheHit?.column ?? 0; + for (let i = current; i <= this.#codeParts.length; i++) { + if (current === offset) return { + line, + column + }; + if (current % CACHE_THRESHOLD === 0) this.#cache.set(current, { + line, + column + }); + if (this.#codeParts[i] === "\n") { + line++; + column = 0; + } else column++; + current++; + } + return { + line, + column + }; + } + getLoc(node) { + const startNeedle = this.offsetToNeedle(node.start); + const start = getPosition(startNeedle, this.#map); + if (start === null) return null; + const endNeedle = this.offsetToNeedle(node.end); + endNeedle.column -= 1; + let end = getPosition(endNeedle, this.#map); + if (end === null) { + for (let line = endNeedle.line; line >= startNeedle.line && end === null; line--) end = getPosition({ + line, + column: Infinity + }, this.#map); + if (end === null) return null; + } + const loc = { + start, + end + }; + const afterEndMappings = allGeneratedPositionsFor(this.#map, { + source: loc.end.filename, + line: loc.end.line, + column: loc.end.column + 1, + bias: LEAST_UPPER_BOUND + }); + if (afterEndMappings.length === 0) loc.end.column = Infinity; + else for (const mapping of afterEndMappings) { + if (mapping.line === null) continue; + const original = originalPositionFor(this.#map, mapping); + if (original.line === loc.end.line) { + loc.end = { + ...original, + filename: original.source + }; + break; + } + } + const filename = loc.start.filename; + let ignoredLines = this.#ignoredLines.get(filename); + if (!ignoredLines) { + ignoredLines = getIgnoredLines(sourceContentFor(this.#map, filename) ?? tryReadFileSync(filename)); + this.#ignoredLines.set(filename, ignoredLines); + } + if (ignoredLines.has(loc.start.line)) return null; + return loc; + } + getSourceLines(loc, filename) { + const index = this.#map.resolvedSources.findIndex((source) => source === filename || resolve(this.#directory, source) === filename); + const sourcesContent = this.#map.sourcesContent?.[index]; + if (sourcesContent == null) return null; + const lines = sourcesContent.split("\n").slice(loc.start.line - 1, loc.end.line); + lines[0] = lines[0].slice(loc.start.column); + lines[lines.length - 1] = lines[lines.length - 1].slice(0, loc.end.column); + return lines.join("\n"); + } +}; +function getPosition(needle, map) { + let position = originalPositionFor(map, needle); + if (position.source == null) position = originalPositionFor(map, { + column: needle.column, + line: needle.line, + bias: LEAST_UPPER_BOUND + }); + if (position.source == null) return null; + return { + line: position.line, + column: position.column, + filename: position.source + }; +} +function createEmptySourceMap(filename, code) { + const mappings = []; + for (const [line, content] of code.split("\n").entries()) { + const parts = content.match(WORD_PATTERN) || []; + const segments = []; + let column = 0; + for (const part of parts) { + segments.push([ + column, + 0, + line, + column + ]); + column += part.length; + } + mappings.push(segments); + } + return { + version: 3, + mappings, + file: filename, + sources: [filename], + sourcesContent: [code], + names: [] + }; +} +async function getInlineSourceMap(filename, code) { + const match = code.match(INLINE_MAP_PATTERN)?.[1]; + if (!match) return null; + try { + if (match.includes(BASE_64_PREFIX)) { + const encoded = match.split(BASE_64_PREFIX).at(-1) || ""; + const decoded = atob(encoded); + return JSON.parse(decoded); + } + const content = await readFile(resolve(dirname(filename), match), "utf-8"); + return JSON.parse(content); + } catch { + return null; + } +} +function tryReadFileSync(filename) { + try { + return readFileSync(filename, "utf8"); + } catch { + return; + } +} + +//#endregion +//#region src/script-coverage.ts +function normalize(scriptCoverage) { + if (scriptCoverage.functions.length === 0) return []; + const ranges = scriptCoverage.functions.flatMap((fn) => fn.ranges.map((range) => ({ + start: range.startOffset, + end: range.endOffset, + count: range.count, + area: range.endOffset - range.startOffset + }))).sort((a, b) => { + const diff = b.area - a.area; + if (diff !== 0) return diff; + return a.end - b.end; + }); + let maxEnd = 0; + for (const r of ranges) if (r.end > maxEnd) maxEnd = r.end; + const hits = new Uint32Array(maxEnd + 1); + for (const range of ranges) hits.fill(range.count, range.start, range.end + 1); + const normalized = []; + let start = 0; + for (let end = 1; end <= hits.length; end++) { + const isLast = end === hits.length; + const current = isLast ? null : hits[end]; + const previous = hits[start]; + if (current !== previous || isLast) { + normalized.push({ + start, + end: end - 1, + count: previous + }); + start = end; + } + } + return normalized; +} +function getCount(offset, coverages) { + let low = 0; + let high = coverages.length - 1; + while (low <= high) { + const mid = Math.floor((low + high) / 2); + const coverage = coverages[mid]; + if (coverage.start <= offset.startOffset && offset.startOffset <= coverage.end) return coverage.count; + else if (offset.startOffset < coverage.start) high = mid - 1; + else low = mid + 1; + } + return 0; +} + +//#endregion +//#region src/coverage-mapper.ts +var CoverageMapper = class CoverageMapper { + constructor(locator, coverageMapData, ranges, wrapperLength, directory, onIgnoreNode, ignoreNode, ignoreSourceCode) { + this.locator = locator; + this.coverageMapData = coverageMapData; + this.ranges = ranges; + this.wrapperLength = wrapperLength; + this.directory = directory; + this.onIgnoreNode = onIgnoreNode; + this.ignoreNode = ignoreNode; + this.ignoreSourceCode = ignoreSourceCode; + } + static async create(options, onIgnoreNode) { + const filename = fileURLToPath(options.coverage.url); + const directory = dirname(filename); + const map = new TraceMap(options.sourceMap || await getInlineSourceMap(filename, options.code) || createEmptySourceMap(filename, options.code)); + return new CoverageMapper(new Locator(options.code, map, directory), createCoverageMapData(filename, map), normalize(options.coverage), options.wrapperLength || 0, directory, onIgnoreNode, options.ignoreNode, options.ignoreSourceCode); + } + onFunction(node, positions) { + if (this.#onIgnore(node, "function")) return; + const loc = this.locator.getLoc(positions.loc); + if (loc === null) return; + const decl = this.locator.getLoc(positions.decl); + if (decl === null) return; + const covered = getCount({ + startOffset: node.start + this.wrapperLength, + endOffset: node.end + this.wrapperLength + }, this.ranges); + if (this.ignoreSourceCode) { + const current = this.locator.getLoc(node) || loc; + const sources = this.locator.getSourceLines(current, this.#getSourceFilename(current)); + if (sources != null && this.ignoreSourceCode(sources, "function", { + start: { + line: current.start.line, + column: current.start.column + }, + end: { + line: current.end.line, + column: current.end.column + } + })) return; + } + addFunction({ + coverageMapData: this.coverageMapData, + covered, + loc, + decl, + filename: this.#getSourceFilename(loc), + name: getFunctionName(node) + }); + } + onStatement(node, parent) { + if (this.#onIgnore(parent || node, "statement")) return; + const loc = this.locator.getLoc(node); + if (loc === null) return; + const covered = getCount({ + startOffset: (parent || node).start + this.wrapperLength, + endOffset: (parent || node).end + this.wrapperLength + }, this.ranges); + if (this.ignoreSourceCode) { + const current = parent && this.locator.getLoc(parent) || loc; + const sources = this.locator.getSourceLines(current, this.#getSourceFilename(current)); + if (sources != null && this.ignoreSourceCode(sources, "statement", { + start: { + line: current.start.line, + column: current.start.column + }, + end: { + line: current.end.line, + column: current.end.column + } + })) return; + } + addStatement({ + coverageMapData: this.coverageMapData, + loc, + covered, + filename: this.#getSourceFilename(loc) + }); + } + onBranch(type, node, branches) { + if (this.#onIgnore(node, "branch")) return; + const loc = this.locator.getLoc(node); + if (loc === null) return; + const locations = []; + const covered = []; + for (const branch of branches) { + if (!branch) { + locations.push({ + start: { + line: void 0, + column: void 0 + }, + end: { + line: void 0, + column: void 0 + } + }); + const count = getCount({ + startOffset: node.start + this.wrapperLength, + endOffset: node.end + this.wrapperLength + }, this.ranges); + const previous = covered.at(-1) || 0; + covered.push(count - previous); + continue; + } + const location = this.locator.getLoc(branch); + if (location !== null) locations.push(location); + const bias = branch.type === "BlockStatement" ? 1 : 0; + covered.push(getCount({ + startOffset: branch.start + bias + this.wrapperLength, + endOffset: branch.end - bias + this.wrapperLength + }, this.ranges)); + } + if (type === "if") { + if (locations.length > 0) locations[0] = loc; + } + if (locations.length === 0) return; + if (this.ignoreSourceCode) { + const sources = this.locator.getSourceLines(loc, this.#getSourceFilename(loc)); + if (sources != null && this.ignoreSourceCode(sources, "branch", { + start: { + line: loc.start.line, + column: loc.start.column + }, + end: { + line: loc.end.line, + column: loc.end.column + } + })) return; + } + addBranch({ + coverageMapData: this.coverageMapData, + loc, + locations, + type, + covered, + filename: this.#getSourceFilename(loc) + }); + } + generate() { + this.locator.reset(); + return this.coverageMapData; + } + #getSourceFilename(position) { + const sourceFilename = position.start.filename || position.end.filename; + if (!sourceFilename) throw new Error(`Missing original filename for ${JSON.stringify(position, null, 2)}`); + if (sourceFilename.startsWith("file://")) return fileURLToPath(sourceFilename); + return resolve(this.directory, sourceFilename); + } + #onIgnore(node, type) { + if (!this.ignoreNode) return false; + const scope = this.ignoreNode(node, type); + if (scope === "ignore-this-and-nested-nodes") this.onIgnoreNode(node); + return scope; + } +}; + +//#endregion +//#region src/index.ts +/** +* Maps V8 `ScriptCoverage` to Istanbul's `CoverageMap`. +* Results are identical with `istanbul-lib-instrument`. +*/ +async function convert(options) { + const ignoreHints = getIgnoreHints(options.code); + if (ignoreHints.length === 1 && ignoreHints[0].type === "file") return {}; + const walker = getWalker(); + const mapper = await CoverageMapper.create(options, walker.onIgnore); + const ast = await options.ast; + await walker.walk(ast, ignoreHints, options.ignoreClassMethods, { + onFunctionDeclaration(node) { + mapper.onFunction(node, { + loc: node.body, + decl: node.id || { + ...node, + end: node.start + 1 + } + }); + }, + onFunctionExpression(node) { + if (isCovered(node)) return; + mapper.onFunction(node, { + loc: node.body, + decl: node.id || { + ...node, + end: node.start + 1 + } + }); + }, + onArrowFunctionExpression(node) { + mapper.onFunction(node, { + loc: node.body, + decl: { + ...node, + end: node.start + 1 + } + }); + if (node.body.type !== "BlockStatement") mapper.onStatement(node.body, node); + }, + onMethodDefinition(node) { + if (node.value.type === "FunctionExpression") setCovered(node.value); + mapper.onFunction(node, { + loc: node.value.body, + decl: node.key + }); + }, + onProperty(node) { + if (node.value.type === "FunctionExpression") { + setCovered(node.value); + mapper.onFunction(node, { + loc: node.value.body, + decl: node.key + }); + } + }, + onClassMethod(babelNode) { + const node = { + type: "FunctionExpression", + start: babelNode.start, + end: babelNode.end, + body: { + type: "BlockStatement", + start: babelNode.body.start, + end: babelNode.body.end, + body: [] + }, + params: [] + }; + mapper.onFunction(node, { + loc: node.body, + decl: { + start: babelNode.key.start, + end: babelNode.key.end + } + }); + }, + onObjectMethod(babelNode) { + const node = { + type: "FunctionExpression", + start: babelNode.start, + end: babelNode.end, + body: { + type: "BlockStatement", + start: babelNode.body.start, + end: babelNode.body.end, + body: [] + }, + params: [] + }; + mapper.onFunction(node, { + loc: node.body, + decl: { + start: babelNode.key.start, + end: babelNode.key.end + } + }); + }, + onBreakStatement: (node) => mapper.onStatement(node), + onContinueStatement: (node) => mapper.onStatement(node), + onDebuggerStatement: (node) => mapper.onStatement(node), + onReturnStatement: (node) => mapper.onStatement(node), + onThrowStatement: (node) => mapper.onStatement(node), + onTryStatement: (node) => mapper.onStatement(node), + onForStatement: (node) => mapper.onStatement(node), + onForInStatement: (node) => mapper.onStatement(node), + onForOfStatement: (node) => mapper.onStatement(node), + onWhileStatement: (node) => mapper.onStatement(node), + onDoWhileStatement: (node) => mapper.onStatement(node), + onWithStatement: (node) => mapper.onStatement(node), + onLabeledStatement: (node) => mapper.onStatement(node), + onExpressionStatement(node) { + if (node.expression.type === "Literal" && node.expression.value === "use strict") return; + mapper.onStatement(node); + }, + onVariableDeclarator(node) { + if (node.init) mapper.onStatement(node.init, node); + }, + onClassBody(node) { + for (const child of node.body) if ((child.type === "PropertyDefinition" || child.type === "ClassProperty" || child.type === "ClassPrivateProperty") && child.value) mapper.onStatement(child.value); + }, + onIfStatement(node, branches) { + mapper.onBranch("if", node, branches); + mapper.onStatement(node); + }, + onConditionalExpression(node, branches) { + mapper.onBranch("cond-expr", node, branches); + }, + onLogicalExpression(node, branches) { + mapper.onBranch("binary-expr", node, branches); + }, + onSwitchStatement(node, cases) { + mapper.onBranch("switch", node, cases); + mapper.onStatement(node); + }, + onAssignmentPattern(node) { + mapper.onBranch("default-arg", node, [node.right]); + } + }); + return mapper.generate(); +} +const coveredNodes = /* @__PURE__ */ new WeakSet(); +function setCovered(node) { + coveredNodes.add(node); +} +function isCovered(node) { + return coveredNodes.has(node); +} + +//#endregion +export { convert, convert as default };
\ No newline at end of file diff --git a/vanilla/node_modules/ast-v8-to-istanbul/package.json b/vanilla/node_modules/ast-v8-to-istanbul/package.json new file mode 100644 index 0000000..d3cae0b --- /dev/null +++ b/vanilla/node_modules/ast-v8-to-istanbul/package.json @@ -0,0 +1,71 @@ +{ + "name": "ast-v8-to-istanbul", + "version": "0.3.11", + "description": "AST-aware v8-to-istanbul", + "homepage": "https://github.com/AriPerkkio/ast-v8-to-istanbul", + "bugs": "https://github.com/AriPerkkio/ast-v8-to-istanbul", + "license": "MIT", + "author": "Ari Perkkiö <ari.perkkio@gmail.com>", + "repository": { + "type": "git", + "url": "git+https://github.com/AriPerkkio/ast-v8-to-istanbul.git" + }, + "files": [ + "dist" + ], + "type": "module", + "types": "./dist/index.d.mts", + "exports": "./dist/index.mjs", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" + }, + "devDependencies": { + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6", + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@types/estree": "^1.0.8", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-lib-instrument": "^1.7.8", + "@types/istanbul-lib-report": "^3.0.3", + "@types/istanbul-lib-source-maps": "^4.0.4", + "@types/istanbul-reports": "^3.0.4", + "@types/node": "^25.0.10", + "@vitejs/plugin-vue": "^6.0.3", + "@vitest/coverage-istanbul": "^4.0.18", + "@vitest/coverage-v8": "^4.0.18", + "acorn": "^8.15.0", + "gh-pages": "^6.3.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-instrument": "^6.0.3", + "istanbul-lib-report": "^3.0.1", + "istanbul-lib-source-maps": "^5.0.6", + "istanbul-reports": "^3.2.0", + "magic-string": "^0.30.21", + "oxc-parser": "^0.110.0", + "oxfmt": "^0.27.0", + "oxlint": "^1.42.0", + "oxlint-tsgolint": "^0.11.2", + "publint": "^0.3.17", + "rollup": "^4.56.0", + "tinyrainbow": "^3.0.3", + "tsdown": "^0.20.1", + "typescript": "^5.9.3", + "vite": "^7.3.1", + "vitest": "^4.0.18", + "vue": "^3.5.27", + "yaml": "^2.8.2" + }, + "scripts": { + "dev": "tsdown --watch --sourcemap", + "build": "tsdown", + "deploy": "touch coverage/.nojekyll && gh-pages --dist coverage --dotfiles", + "test": "vitest", + "test:dev": "vitest --project 'vite/parseAstAsync'", + "lint": "oxlint --type-aware && oxfmt --check && publint", + "typecheck": "tsc --noEmit", + "debug": "open fixture-coverage/index.html istanbul-coverage/index.html", + "verify": "pnpm build; pnpm lint; pnpm typecheck; pnpm run test --run; pnpm bench" + } +}
\ No newline at end of file |
