diff --git a/.gitignore b/.gitignore index bd9469a..9b79c94 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,6 @@ /dist /build* -/transform/*.mjs -/transform/tsconfig.tsbuildinfo /coverage /coverage-ts diff --git a/.prettierignore b/.prettierignore index 1ffc659..a76c867 100644 --- a/.prettierignore +++ b/.prettierignore @@ -2,7 +2,6 @@ /coverage-ts /dist /node_modules -/transform/*.mjs /tests/ts/fixture /third_party /tests/cpp/lit/expectInstrument**/*.json diff --git a/as-test.config.js b/as-test.config.js index cd16a9e..78c0619 100644 --- a/as-test.config.js +++ b/as-test.config.js @@ -1,30 +1,15 @@ +/** + * @type {import("./config.d.ts").Config} + */ export default { - /** file include in test */ include: ["assembly", "tests/as"], - - /** optional: file exclude */ exclude: [], - - /** optional: assemblyscript compile flag, default is --exportStart _start -O0 */ flags: "", - - /** - * optional: import functions - * @param {ImportsArgument} runtime - * @returns - */ imports(runtime) { return {}; }, - - /** optional: template file path, default "coverage" */ temp: "coverage", - - /** optional: report file path, default "coverage" */ output: "coverage", - - /** optional: test result output format, default "table" */ mode: ["html", "json", "table"], - isolated: false, }; diff --git a/bin/cli.js b/bin/cli.js index e65f71e..ae90a17 100755 --- a/bin/cli.js +++ b/bin/cli.js @@ -88,6 +88,8 @@ const getBoolean = (optionValue, configValue) => { const isolatedInConfig = getBoolean(options.isolated, config.isolated); const isolated = isolatedInConfig ?? false; +const entryFiles = config.entryFiles ?? null; + /** * @type {import("../dist/interface.d.ts").TestOption} */ @@ -95,6 +97,8 @@ const testOption = { includes, excludes, testFiles, + entryFiles, + testNamePattern: testNamePattern, collectCoverage, onlyFailures, diff --git a/config.d.ts b/config.d.ts index 8a6fabf..a9e8121 100644 --- a/config.d.ts +++ b/config.d.ts @@ -1,4 +1,4 @@ -import { Imports } from "./dist/interface.d.ts"; +import type { Imports } from "./dist/interface.d.ts"; export type OutputMode = "html" | "json" | "table"; @@ -7,6 +7,8 @@ export declare class Config { include: string[]; /** Files to exclude from testing and coverage statistics, has higher priority than include */ exclude?: string[]; + /** entry files for the whole projects, used to collect all source code information. default value is `${include}/index.ts` */ + entryFiles?: string[]; /** whether to collect coverage information, default is true */ collectCoverage?: boolean; @@ -14,7 +16,7 @@ export declare class Config { /** create an wasm instance for each test files. default is false (will be true in next major version) */ isolated?: boolean; - /** assemblyscript compile flag, default is --exportStart _start --sourceMap --debug -O0 */ + /** assemblyscript compile flag, default is --exportStart __unit_test_start --sourceMap --debug -O0 */ flags?: string; imports?: Imports; diff --git a/docs/release-note.md b/docs/release-note.md index 541aa7d..4935c38 100644 --- a/docs/release-note.md +++ b/docs/release-note.md @@ -6,6 +6,10 @@ - Changed the default value of `isolated` from `true` to `false`. +🛠️ Improvements + +- Introduce new configuration `entryFiles` to reach all source code. ([#88](https://github.com/wasm-ecosystem/assemblyscript-unittest-framework/pull/88)) + ## 1.4.1 🚀 Highlight Features diff --git a/docs/technical-details/code-debug-info.md b/docs/technical-details/code-debug-info.md index dfdd6d6..c0410ec 100644 --- a/docs/technical-details/code-debug-info.md +++ b/docs/technical-details/code-debug-info.md @@ -19,22 +19,4 @@ } ``` -```ts -interface CodeDebugInfo { - debugFiles: string[]; - debugInfos: Record; -} - -interface FunctionDebugInfo { - index: number; - branchInfo: Branch[]; - lineInfo: LineInfos; -} - -type LineRange = [FileIndex, LineIndex, ColumnIndex][][]; -type LineInfos = LineRange[][]; -type FileIndex = number; -type LineIndex = number; -type ColumnIndex = number; -type Branch = [number, number]; -``` +The schema corresponding to json can be found in interface `DebugInfo` in `src/interface.ts`. diff --git a/eslint.config.mjs b/eslint.config.mjs index b31bd3a..9c1c644 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -10,6 +10,7 @@ export default [ { rules: { "unicorn/no-array-for-each": "off", + "sonarjs/fixme-tag": "off", }, }, ]; diff --git a/instrumentation/BasicBlockAnalysis.hpp b/instrumentation/BasicBlockAnalysis.hpp index be66685..105b87e 100644 --- a/instrumentation/BasicBlockAnalysis.hpp +++ b/instrumentation/BasicBlockAnalysis.hpp @@ -15,14 +15,6 @@ namespace wasmInstrumentation { /// class BasicBlockAnalysis final { public: - /// - /// @brief Add include file to debug info analysis - /// - /// @param include - inline void addInclude(const std::string &&include) noexcept { - includes.emplace_back(include); - } - /// /// @brief Add exclude file to debug info analysis /// diff --git a/instrumentation/CoverageInstru.cpp b/instrumentation/CoverageInstru.cpp index 6b13482..77f9a26 100644 --- a/instrumentation/CoverageInstru.cpp +++ b/instrumentation/CoverageInstru.cpp @@ -11,15 +11,6 @@ void CoverageInstru::innerAnalysis(BasicBlockAnalysis &basicBlockAnalysis) const Json::Reader jsonReader; Json::Value includesJsonValue; Json::Value excludesJsonValue; - if (!config->includes.empty()) { - jsonReader.parse(std::string(config->includes), includesJsonValue); - if (includesJsonValue.isArray()) { - const uint32_t includesJsonSize = includesJsonValue.size(); - for (uint32_t i = 0U; i < includesJsonSize; ++i) { - basicBlockAnalysis.addInclude(includesJsonValue[i].asString()); - } - } - } if (!config->excludes.empty()) { jsonReader.parse(std::string(config->excludes), excludesJsonValue); if (excludesJsonValue.isArray()) { @@ -181,8 +172,8 @@ extern "C" EMSCRIPTEN_KEEPALIVE wasmInstrumentation::InstrumentationResponse wasm_instrument(char const *const fileName, char const *const targetName, char const *const reportFunction, char const *const sourceMap, char const *const expectInfoOutputFilePath, - char const *const debugInfoOutputFilePath, char const *const includes, - char const *const excludes, bool skipLib, bool collectCoverage) noexcept { + char const *const debugInfoOutputFilePath, char const *const excludes, bool skipLib, + bool collectCoverage) noexcept { wasmInstrumentation::InstrumentationConfig config; config.fileName = fileName; @@ -191,7 +182,6 @@ wasm_instrument(char const *const fileName, char const *const targetName, config.sourceMap = sourceMap; config.expectInfoOutputFilePath = expectInfoOutputFilePath; config.debugInfoOutputFilePath = debugInfoOutputFilePath; - config.includes = includes; config.excludes = excludes; config.skipLib = skipLib; config.collectCoverage = collectCoverage; diff --git a/instrumentation/CoverageInstru.hpp b/instrumentation/CoverageInstru.hpp index 340b1e7..77496fd 100644 --- a/instrumentation/CoverageInstru.hpp +++ b/instrumentation/CoverageInstru.hpp @@ -40,7 +40,6 @@ class InstrumentationConfig final { std::string_view reportFunction; ///< trace report function name std::string_view sourceMap; ///< input source map file name std::string_view debugInfoOutputFilePath; ///< debug info output file name - std::string_view includes; ///< function include filter std::string_view excludes; ///< function exclude filter std::string_view expectInfoOutputFilePath; ///< exception info output file name bool skipLib = true; ///< if skip lib functions @@ -56,7 +55,7 @@ class InstrumentationConfig final { const InstrumentationConfig &instance) noexcept { out << "filename: " << instance.fileName << ", targetName: " << instance.targetName << ", sourceMap: " << instance.sourceMap << ", reportFunction:" << instance.reportFunction - << ", includes: " << instance.includes << ", excludes: " << instance.excludes + << ", excludes: " << instance.excludes << ", expectInfoOutputFilePath: " << instance.expectInfoOutputFilePath << ", skipLib: " << std::boolalpha << instance.skipLib << ", collectCoverage: " << std::boolalpha << instance.collectCoverage << std::endl; @@ -120,7 +119,7 @@ extern "C" EMSCRIPTEN_KEEPALIVE wasmInstrumentation::InstrumentationResponse wasm_instrument(char const *const fileName, char const *const targetName, char const *const reportFunction, char const *const sourceMap, char const *const expectInfoOutputFilePath, - char const *const debugInfoOutputFilePath, char const *const includes = NULL, - char const *const excludes = NULL, bool skipLib = true, bool collectCoverage = true) noexcept; + char const *const debugInfoOutputFilePath, char const *const excludes, bool skipLib, + bool collectCoverage) noexcept; #endif #endif diff --git a/instrumentation/wasm-instrumentation.d.ts b/instrumentation/wasm-instrumentation.d.ts index c8153dc..ebbe667 100644 --- a/instrumentation/wasm-instrumentation.d.ts +++ b/instrumentation/wasm-instrumentation.d.ts @@ -9,7 +9,6 @@ interface Instrumenter { sourceMap: number, expectInfoOutputFilePath: number, debugInfoOutputFilePath: number, - includes: number, excludes: number, skipLib: boolean, collectCoverage: boolean diff --git a/package.json b/package.json index 6733da0..8ac17f0 100644 --- a/package.json +++ b/package.json @@ -12,17 +12,17 @@ }, "prettier": "@schleifner/prettier-config", "scripts": { - "watch:ts": "concurrently --kill-others \"tsc --build ./src/tsconfig.json --watch\" \"tsc --build ./transform/tsconfig.json --watch\"", - "build": "node scripts/build_instrumentation.js -j 2 && tsc --build ./transform/tsconfig.json && tsc --build ./src/tsconfig.json", + "watch:ts": "tsc --build ./src/tsconfig.json --watch", + "build": "node scripts/build_instrumentation.js -j 2 && tsc --build ./src/tsconfig.json", "test:as": "node bin/as-test.js", "test:ts": "cross-env NODE_OPTIONS=--experimental-vm-modules jest", "test:cpp": "cmake -B build -S . && cmake --build build --parallel 2 --target wasm-instrumentation-test wasm-opt && build/bin/wasm-instrumentation-test", "test:e2e": " node tests/e2e/run.js", "test": "npm run test:as && npm run test:ts && npm run test:cpp && npm run test:e2e", - "lint:ts": "eslint src transform tests/ts/test --max-warnings=0", + "lint:ts": "eslint src tests/ts/test --max-warnings=0", "lint:as": "npx eslint --config ./assembly/eslint.config.mjs assembly --max-warnings=0", "lint": "npm run lint:ts && prettier -c .", - "lint:fix:ts": "eslint src transform tests/ts/test --fix && prettier --write .", + "lint:fix:ts": "eslint src tests/ts/test --fix && prettier --write .", "docs:dev": "vitepress dev docs", "docs:build": "vitepress build docs" }, @@ -83,7 +83,6 @@ "dist/**/*", "docs/**/*", "resource/**/*", - "transform/**/*", "LICENSE", "README.md" ] diff --git a/src/core/analyze.ts b/src/core/analyze.ts new file mode 100644 index 0000000..e96fd34 --- /dev/null +++ b/src/core/analyze.ts @@ -0,0 +1,83 @@ +/** + * Will transform all source file to get all relative functions + */ + +import ignore from "ignore"; +import { join, relative, resolve } from "node:path"; +import { getIncludeFiles } from "../utils/pathResolver.js"; +import { TestOption } from "../interface.js"; +import assert from "node:assert"; + +type AnalyzeOption = Pick; + +interface UnittestPackage { + readonly testCodePaths: string[]; + readonly sourceCodePaths: string[]; + readonly entryFiles: string[]; + readonly filterByName: (fullTestName: string) => boolean; +} + +export function analyze( + { includes, excludes, testNamePattern, testFiles, entryFiles }: AnalyzeOption, + failedTestNames: string[] +): UnittestPackage { + const testCodePaths = testFiles ?? getRelatedFiles(includes, excludes, (path: string) => path.endsWith(".test.ts")); + const sourceCodePaths = getRelatedFiles( + includes, + excludes, + (path: string) => path.endsWith(".ts") && !path.endsWith(".test.ts") + ); + return { + // if specify testFiles, use testFiles for unittest + // otherwise, get testFiles(*.test.ts) in includes directory + testCodePaths: testCodePaths, + // get all source files in includes directory + sourceCodePaths: sourceCodePaths, + entryFiles: entryFiles ?? getEntryFiles(includes, sourceCodePaths), + filterByName: getFilterByName(testNamePattern, failedTestNames), + }; +} + +function getEntryFiles(includes: string[], sourceCodePaths: string[]): string[] { + // entry files must be in source code paths + return includes + .map((include) => (include.endsWith(".ts") ? include : join(include, "index.ts"))) + .filter((include) => include.endsWith(".ts") && sourceCodePaths.includes(include)); +} + +function getFilterByName(testNamePattern: string | null, failedTestNames: string[]): UnittestPackage["filterByName"] { + assert( + !(testNamePattern !== null && failedTestNames.length > 0), + "Cannot use testNamePattern and failedTestNames together" + ); + if (testNamePattern !== null) { + const regexPattern = new RegExp(testNamePattern); + return (fullTestName: string): boolean => regexPattern.test(fullTestName); + } + if (failedTestNames.length > 0) { + return (fullTestName: string): boolean => failedTestNames.includes(fullTestName); + } + return (): boolean => true; +} + +// a. include in config +// b. exclude in config +function getRelatedFiles(includes: string[], excludes: string[], filter: (path: string) => boolean) { + const result: string[] = []; + const includeFiles = getIncludeFiles(includes, (path) => path.endsWith(".ts")); // a + const exc = ignore().add(excludes); + + for (const path of includeFiles) { + const relativePath = relative(".", path); + if (relativePath.startsWith("..")) { + throw new Error(`file ${path} out of scope (${resolve(".")})`); + } + if (exc.ignores(relativePath)) { + continue; // ab + } + if (filter(path)) { + result.push(path.replaceAll(/\\/g, "/")); + } + } + return result; +} diff --git a/src/core/compile.ts b/src/core/compile.ts index 70e4a7e..ecf478f 100644 --- a/src/core/compile.ts +++ b/src/core/compile.ts @@ -5,9 +5,11 @@ import { TestOption } from "../interface.js"; export type CompileOption = Pick; -export async function compile(testCodePaths: string[], option: CompileOption): Promise { +export async function compile(testCodePaths: string[], entryFiles: string[], option: CompileOption): Promise { const { isolated } = option; - return isolated ? await separatedCompile(testCodePaths, option) : [await unifiedCompile(testCodePaths, option)]; + return isolated + ? await separatedCompile(testCodePaths, entryFiles, option) + : [await unifiedCompile(testCodePaths, entryFiles, option)]; } function getNewPath(newFolder: string, oldFolder: string, srcPath: string): string { @@ -22,7 +24,8 @@ function getAscArgs(sources: string[], outputWasm: string, outputWat: string, fl "--textFile", outputWat, "--exportStart", - "_start", + // avoid name conflict with user-defined start functions + "__unit_test_start", "--sourceMap", "--debug", "-O0", @@ -34,16 +37,20 @@ function getAscArgs(sources: string[], outputWasm: string, outputWat: string, fl return ascArgv; } -async function unifiedCompile(testCodePaths: string[], option: CompileOption): Promise { +async function unifiedCompile(testCodePaths: string[], entryFiles: string[], option: CompileOption): Promise { const { outputFolder, flags } = option; const outputWasm = join(outputFolder, "test.wasm").replaceAll(/\\/g, "/"); const outputWat = join(outputFolder, "test.wat").replaceAll(/\\/g, "/"); - const ascArgv = getAscArgs(testCodePaths, outputWasm, outputWat, flags); + const ascArgv = getAscArgs(testCodePaths.concat(entryFiles), outputWasm, outputWat, flags); await ascMain(ascArgv, false); return outputWasm; } -async function separatedCompile(testCodePaths: string[], option: CompileOption): Promise { +async function separatedCompile( + testCodePaths: string[], + entryFiles: string[], + option: CompileOption +): Promise { const { outputFolder, flags } = option; const wasm: string[] = []; const root = findRoot(testCodePaths); @@ -51,7 +58,7 @@ async function separatedCompile(testCodePaths: string[], option: CompileOption): const outputWasm = getNewPath(outputFolder, root, testCodePath).slice(0, -2).concat("wasm"); wasm.push(outputWasm); const outputWat = getNewPath(outputFolder, root, testCodePath).slice(0, -2).concat("wat"); - const ascArgv = getAscArgs([testCodePath], outputWasm, outputWat, flags); + const ascArgv = getAscArgs([testCodePath, ...entryFiles], outputWasm, outputWat, flags); await ascMain(ascArgv, false); }; diff --git a/src/core/execute.ts b/src/core/execute.ts index 4076889..2018b6e 100644 --- a/src/core/execute.ts +++ b/src/core/execute.ts @@ -72,7 +72,7 @@ async function nodeExecutor( await executionRecorder.runTestFunction( `${instrumentResult.baseName} - init`, () => { - wasi.start(ins); + (ins.exports["__unit_test_start"] as () => void)(); }, exceptionHandler ); diff --git a/src/core/instrument.ts b/src/core/instrument.ts index 026682a..2bcb76d 100644 --- a/src/core/instrument.ts +++ b/src/core/instrument.ts @@ -1,15 +1,7 @@ import initInstrumenter from "../../build_wasm/bin/wasm-instrumentation.js"; import { InstrumentResult } from "../interface.js"; -export async function instrument( - sourceWasms: string[], - sourceCodePaths: string[], - collectCoverage: boolean -): Promise { - const includeRegexs = sourceCodePaths.map((path) => { - return `(start:)?${path.slice(0, -3)}.*`; - }); - const includeFilter = JSON.stringify(includeRegexs); +export async function instrument(sourceWasms: string[], collectCoverage: boolean): Promise { const res: InstrumentResult[] = []; const instrumenter = await initInstrumenter(); for (const sourceFile of sourceWasms) { @@ -24,21 +16,9 @@ export async function instrument( const sourceMap = instrumenter.allocateUTF8(result.sourceMap); const debugInfo = instrumenter.allocateUTF8(result.debugInfo); const expectInfo = instrumenter.allocateUTF8(result.expectInfo); - const include = instrumenter.allocateUTF8(includeFilter); - instrumenter._wasm_instrument( - source, - output, - report, - sourceMap, - expectInfo, - debugInfo, - include, - 0, - true, - collectCoverage - ); - for (const ptr of [source, output, report, sourceMap, debugInfo, expectInfo, include]) { + instrumenter._wasm_instrument(source, output, report, sourceMap, expectInfo, debugInfo, 0, true, collectCoverage); + for (const ptr of [source, output, report, sourceMap, debugInfo, expectInfo]) { instrumenter._free(ptr); } res.push(result); diff --git a/src/core/precompile.ts b/src/core/precompile.ts deleted file mode 100644 index 2c3892b..0000000 --- a/src/core/precompile.ts +++ /dev/null @@ -1,93 +0,0 @@ -/** - * Will transform all source file to get all relative functions - */ - -import ignore from "ignore"; -import { join, relative, resolve } from "node:path"; -import { getIncludeFiles } from "../utils/pathResolver.js"; -import { SourceFunctionInfo, UnittestPackage } from "../interface.js"; -import { projectRoot } from "../utils/projectRoot.js"; -import { ascMain } from "../utils/ascWrapper.js"; -import assert from "node:assert"; - -function getFilterByName(testNamePattern: string | null, failedTestNames: string[]): UnittestPackage["filterByName"] { - assert( - !(testNamePattern !== null && failedTestNames.length > 0), - "Cannot use testNamePattern and failedTestNames together" - ); - if (testNamePattern !== null) { - const regexPattern = new RegExp(testNamePattern); - return (fullTestName: string): boolean => regexPattern.test(fullTestName); - } - if (failedTestNames.length > 0) { - return (fullTestName: string): boolean => failedTestNames.includes(fullTestName); - } - return (): boolean => true; -} - -export async function precompile( - includes: string[], - excludes: string[], - testFiles: string[] | undefined, // this field specifed test file names - testNamePattern: string | null, - failedTestNames: string[], - collectCoverage: boolean, - flags: string -): Promise { - let sourceFunctions: Map | undefined = undefined; - if (collectCoverage) { - const sourceCodePaths = getRelatedFiles(includes, excludes, (path: string) => !path.endsWith(".test.ts")); - const sourceTransformFunction = join(projectRoot, "transform", "listFunctions.mjs"); - globalThis.__functionInfos = undefined; - sourceFunctions = await transform(sourceTransformFunction, sourceCodePaths, flags, () => __functionInfos); - } - return { - // if specify testFiles, use testFiles for unittest - // otherwise, get testFiles(*.test.ts) in includes directory - testCodePaths: testFiles ?? getRelatedFiles(includes, excludes, (path: string) => path.endsWith(".test.ts")), - filterByName: getFilterByName(testNamePattern, failedTestNames), - sourceFunctions: sourceFunctions || new Map(), - }; -} - -async function transform( - transformFunction: string, - codePath: string | string[], - flags: string, - collectCallback: () => T -) { - let ascArgv = ["--noEmit", "--disableWarning", "--transform", transformFunction, "-O0"]; - if (typeof codePath === "string") { - ascArgv.push(codePath); - } else { - ascArgv.push(...codePath); - } - if (flags) { - const argv = flags.split(" "); - ascArgv = ascArgv.concat(argv); - } - await ascMain(ascArgv, true); - return collectCallback(); -} - -// a. include in config -// b. exclude in config -export function getRelatedFiles(includes: string[], excludes: string[], filter: (path: string) => boolean) { - const result: string[] = []; - const includeFiles = getIncludeFiles(includes, (path) => path.endsWith(".ts")); // a - const exc = ignore().add(excludes); - - for (const path of includeFiles) { - const relativePath = relative(".", path); - if (relativePath.startsWith("..")) { - throw new Error(`file ${path} out of scope (${resolve(".")})`); - } - if (exc.ignores(relativePath)) { - continue; // ab - } - if (filter(path)) { - result.push(path.replaceAll(/\\/g, "/")); - } - } - return result; -} diff --git a/src/index.ts b/src/index.ts index 20489ef..0eb5357 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,7 +2,7 @@ import chalk from "chalk"; import pkg from "fs-extra"; import { Parser } from "./parser/index.js"; import { compile } from "./core/compile.js"; -import { precompile } from "./core/precompile.js"; +import { analyze } from "./core/analyze.js"; import { instrument } from "./core/instrument.js"; import { execWasmBinaries } from "./core/execute.js"; import { generateReport, reportConfig } from "./generator/index.js"; @@ -48,37 +48,23 @@ async function startUniTestImpl(options: TestOption): Promise { emptydirSync(options.outputFolder); emptydirSync(options.tempFolder); - const unittestPackage = await precompile( - options.includes, - options.excludes, - options.testFiles, - options.testNamePattern, - failedTestCases, - options.collectCoverage, - options.flags - ); + const { sourceCodePaths, testCodePaths, entryFiles, filterByName } = analyze(options, failedTestCases); console.log(chalk.blueBright("code analysis: ") + chalk.bold.greenBright("OK")); - const wasmPaths = await compile(unittestPackage.testCodePaths, options); + const wasmPaths = await compile(testCodePaths, entryFiles, options); console.log(chalk.blueBright("compile test files: ") + chalk.bold.greenBright("OK")); - const sourcePaths = unittestPackage.sourceFunctions ? Array.from(unittestPackage.sourceFunctions.keys()) : []; - const instrumentResult = await instrument(wasmPaths, sourcePaths, options.collectCoverage); + const instrumentResult = await instrument(wasmPaths, options.collectCoverage); console.log(chalk.blueBright("instrument: ") + chalk.bold.greenBright("OK")); - const executedResult = await execWasmBinaries( - options.tempFolder, - instrumentResult, - unittestPackage.filterByName, - options.imports - ); + const executedResult = await execWasmBinaries(options.tempFolder, instrumentResult, filterByName, options.imports); console.log(chalk.blueBright("execute test files: ") + chalk.bold.greenBright("OK")); await executedResult.writeFailures(failurePath); executedResult.print(console.log); if (options.collectCoverage) { const parser = new Parser(); - const fileCoverageInfo = await parser.parse(instrumentResult, unittestPackage.sourceFunctions!); + const fileCoverageInfo = await parser.parse(instrumentResult, sourceCodePaths); reportConfig.warningLimit = options.warnLimit || reportConfig.warningLimit; reportConfig.errorLimit = options.errorLimit || reportConfig.errorLimit; generateReport(options.mode, options.outputFolder, fileCoverageInfo); diff --git a/src/interface.ts b/src/interface.ts index c600749..54560b0 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -172,17 +172,6 @@ export class CodeCoverage { } } -export interface UnittestPackage { - readonly testCodePaths: string[]; - readonly filterByName: (fullTestName: string) => boolean; - readonly sourceFunctions?: Map; -} - -export interface SourceFunctionInfo { - name: string; - range: [number, number]; -} - export interface TestNameInfo { testName: string; testFilePath: string; @@ -200,7 +189,9 @@ export type Imports = ((arg: ImportsArgument) => Record) | null export interface TestOption { includes: string[]; excludes: string[]; - testFiles?: string[]; + entryFiles: string[] | null; + + testFiles: string[] | undefined; testNamePattern: string | null; collectCoverage: boolean; onlyFailures: boolean; diff --git a/src/parser/index.ts b/src/parser/index.ts index 9dcfacb..2488828 100644 --- a/src/parser/index.ts +++ b/src/parser/index.ts @@ -1,6 +1,6 @@ import fs from "fs-extra"; import { readFile } from "node:fs/promises"; -import { checkFunctionName, checkGenerics, isIncluded, json2map } from "../utils/index.js"; +import { isFunctionInsideFile, checkGenerics, json2map, checkVarargs } from "../utils/index.js"; import { SingleFileCoverageAnalysis } from "./singleFileAnalysis.js"; import { SingleFunctionCoverageAnalysis } from "./singleFunctionAnalysis.js"; import { @@ -12,7 +12,6 @@ import { FunctionIndex, LineInfoMap, FunctionCoverageResult, - SourceFunctionInfo, LineRange, InstrumentResult, } from "../interface.js"; @@ -25,15 +24,12 @@ export class Parser { /** key: functionName, value: { branchInfo, lineInfo } */ functionCovInfoMap = new Map(); - async parse( - instrumentResults: InstrumentResult[], - sourceFunctions: Map - ): Promise { + async parse(instrumentResults: InstrumentResult[], sourceCodePaths: string[]): Promise { for (const instrumentResult of instrumentResults) { await this.traceParse(instrumentResult); } this.generateFunctionCoverage(); - await this.generateFileCoverage(sourceFunctions); + await this.generateFileCoverage(sourceCodePaths); return this.fileCoverageResults; } @@ -87,7 +83,7 @@ export class Parser { throw new Error(`unknown error: not find fileIndex ${range[0]} in ${instrumentResult.debugInfo}`); } // if basicBlock is inline function from other files, ignore it - return checkFunctionName(filename, name); + return isFunctionInsideFile(filename, name); }) .map((range) => range[1]); // basic block index start at 0 @@ -139,37 +135,36 @@ export class Parser { * And then, find out which sourceFunction has been tested and which sourceFunction has not been tested. * based on the function name match or lineRange match. * Finally, merge the functionCoverageResult to fileCoverageResult. - * @param sourceFunctions functionInfo of source file generated by transform. */ - async generateFileCoverage(sourceFunctions: Map) { - for (const [sourceCodePath, sourceFunctionInfos] of sourceFunctions.entries()) { + async generateFileCoverage(sourceCodePaths: string[]) { + for (const sourceCodePath of sourceCodePaths) { const source = await readFile(sourceCodePath, { encoding: "utf8" }); const singleFileAnalysis = new SingleFileCoverageAnalysis(sourceCodePath, source); - singleFileAnalysis.setTotalFunction(sourceFunctionInfos.length); + const functionCovInfosInCurrentFile = this.functionCoverageResults.filter((result) => + isFunctionInsideFile(sourceCodePath, result.functionName) + ); - const testedFunctions = new Set(); - const singleFileFunctionCovInfos = this.functionCoverageResults - .filter((result) => checkFunctionName(sourceCodePath, result.functionName)) - .filter((result) => { - const index = sourceFunctionInfos.findIndex( - // For each FunctionCoverageResult : - // check If one of the SourceFunctions can be matched with result by name matching and line range matching - (v) => result.functionName === v.name || isIncluded(result.lineRange, v.range) - ); - if (index !== -1) { - testedFunctions.add(index); - return true; - } - return false; - }); - singleFileAnalysis.merge(singleFileFunctionCovInfos); + singleFileAnalysis.setTotalFunction(getTotalFunctionCount(this.functionCovInfoMap, sourceCodePath)); - const unTestedFunctionRanges = sourceFunctionInfos - .filter((info, index) => !testedFunctions.has(index)) - .map((info) => info.range); - singleFileAnalysis.setUnTestedFunction(unTestedFunctionRanges); + singleFileAnalysis.merge(functionCovInfosInCurrentFile); this.fileCoverageResults.push(singleFileAnalysis.getResult()); } } } + +function getTotalFunctionCount(functionCovInfoMap: Map, sourceCodePath: string): number { + const totalFunctionCount = new Set( + functionCovInfoMap + .keys() + .map((functionName) => cleanupFunctionVariants(functionName) ?? functionName) + .filter((functionName) => isFunctionInsideFile(sourceCodePath, functionName)) + ).size; + return totalFunctionCount; +} + +function cleanupFunctionVariants(functionName: string): string { + functionName = checkGenerics(functionName) ?? functionName; + functionName = checkVarargs(functionName) ?? functionName; + return functionName; +} diff --git a/src/parser/singleFileAnalysis.ts b/src/parser/singleFileAnalysis.ts index 5c783b9..44f63ad 100644 --- a/src/parser/singleFileAnalysis.ts +++ b/src/parser/singleFileAnalysis.ts @@ -13,19 +13,6 @@ export class SingleFileCoverageAnalysis { this.result.functionCoverageRate.total = count; } - setUnTestedFunction(ranges: [number, number][]) { - for (const range of ranges) { - const [startLine, endLine] = range; - for (let index = startLine - 1; index < endLine; index++) { - const codeCoverage = this.result.sourceUsedCount[index]; - if (codeCoverage === undefined) { - throw new Error(`unknown error: There is no ${index} Line in file ${this.result.filename}`); - } - codeCoverage.usedCount = 0; - } - } - } - merge(results: FunctionCoverageResult[]) { // SingleFileCoverageAnalysis contains FileCoverageResult if (results.length === 0) return; diff --git a/src/type/global.d.ts b/src/type/global.d.ts deleted file mode 100644 index 5b585e2..0000000 --- a/src/type/global.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { SourceFunctionInfo } from "../interface.ts"; - -declare global { - // why use var: [Remove block-scoped bindings from globalThis](https://github.com/microsoft/TypeScript/issues/30547) - // store listFunctions transform results in global - var __functionInfos: Map | undefined; - // store listTestNames transform results in global - var testNames: string[]; -} - -export {}; diff --git a/src/utils/index.ts b/src/utils/index.ts index 5d750ad..ba9fc40 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -19,24 +19,15 @@ export function json2map(json: Record): Map { * @param fileName example: "assembly/assertCollector.ts" * @param functionName example: * "start:assembly/assertCollector~anonymous|0" - * || "assemblly/assertCollector/addDescription" + * || "assembly/assertCollector/addDescription" */ -export function checkFunctionName(fileName: string, functionName: string) { +export function isFunctionInsideFile(fileName: string, functionName: string) { const regex = new RegExp(`^(start:)?${fileName.slice(0, -3)}[/~]`); return regex.test(functionName); } -export function isIncluded(r1: [number, number], r2: [number, number]) { - /** - * range :[startLine, endLine] - * determine if function r1 is included in function r2 by line range - * r1 means function line range info from instrumenting - * r2 means function line range info from transform - */ - return r1[0] >= r2[0] && r1[1] <= r2[1]; -} - export function checkGenerics(functionName: string): string | undefined { + // FIXME: cannot handle nested generic method in generic class const startIndex = functionName.indexOf("<"); const endIndex = functionName.lastIndexOf(">"); if (startIndex !== -1 && endIndex !== -1) { @@ -45,6 +36,13 @@ export function checkGenerics(functionName: string): string | undefined { return undefined; } +export function checkVarargs(functionName: string): string | undefined { + if (functionName.endsWith("@varargs")) { + return functionName.slice(0, -8); + } + return undefined; +} + export function supplyDefaultFunction( infos: ImportFunctionInfo[], importObject: ASImports, diff --git a/tests/cpp/CMakeLists.txt b/tests/cpp/CMakeLists.txt index ea71860..0e01fe5 100644 --- a/tests/cpp/CMakeLists.txt +++ b/tests/cpp/CMakeLists.txt @@ -8,7 +8,7 @@ FetchContent_Declare( ) FetchContent_MakeAvailable(googletest) -file(GLOB test_sources CONFIGURE_DEPENDS *.cpp ./utils/*.cpp) # innclude lit test by default +file(GLOB test_sources CONFIGURE_DEPENDS *.cpp ./utils/*.cpp) # include lit test by default add_executable( wasm-instrumentation-test @@ -19,6 +19,7 @@ target_link_libraries( PUBLIC wasm-instrumentation GTest::gtest_main + GTest::gmock binaryen jsoncpp_static ) diff --git a/tests/cpp/lit.cpp b/tests/cpp/lit.cpp index b59e8d6..6cdbcf9 100644 --- a/tests/cpp/lit.cpp +++ b/tests/cpp/lit.cpp @@ -1,6 +1,5 @@ #include "json/reader.h" #include "json/value.h" -#include #include #include #include @@ -55,7 +54,7 @@ TEST(lit, coverageInstrumentation) { const std::filesystem::path expectTarget = tmpDir / (wast + ".expect.json"); const char *traceFunName = "assembly/env/traceExpression"; wasmInstrumentation::InstrumentationConfig config; - std::cout << "running lit - " << fixtureFolder << "/" << wast << std::endl; + std::cout << "running lit - " << (fixtureFolder / wast) << std::endl; const std::string wasmFileStr = wasmFile.string(); const std::string debugTargetStr = debugTarget.string(); const std::string expectTargetStr = expectTarget.string(); @@ -67,7 +66,6 @@ TEST(lit, coverageInstrumentation) { config.sourceMap = wasmFileMapStr; config.targetName = wasmTargetStr; config.reportFunction = traceFunName; - config.includes = include; config.excludes = ""; wasmInstrumentation::CoverageInstru instrumentor(&config); ASSERT_EQ(instrumentor.instrument(), wasmInstrumentation::InstrumentationResponse::NORMAL); @@ -108,7 +106,6 @@ TEST(lit, expectInstrumentation) { const std::filesystem::path expectTarget = tmpDir / "expect.test.expect.json"; const std::filesystem::path wasmTarget = tmpDir / "expect.test.instrumented.wasm"; const char *traceFunName = "assembly/env/traceExpression"; - const char *include = "[\"tests-as\",\"assembly/.*\"]"; const std::string wasmFileStr = wasmFile.string(); const std::string wasmFileMapStr = wasmFileMap.string(); const std::string debugTargetStr = debugTarget.string(); @@ -120,7 +117,6 @@ TEST(lit, expectInstrumentation) { config.expectInfoOutputFilePath = expectTargetStr; config.targetName = wasmTargetStr; config.reportFunction = traceFunName; - config.includes = include; config.excludes = ""; wasmInstrumentation::CoverageInstru instrumentor(&config); ASSERT_EQ(instrumentor.instrument(), wasmInstrumentation::InstrumentationResponse::NORMAL); diff --git a/tests/cpp/lit/covInstrument/include_exclude.wast b/tests/cpp/lit/covInstrument/include_exclude.wast deleted file mode 100644 index 60f6f4e..0000000 --- a/tests/cpp/lit/covInstrument/include_exclude.wast +++ /dev/null @@ -1,42 +0,0 @@ -(module - (export "main" (func $main)) - (export "shouldExcludeFun" (func $shouldExcludeFun)) - (func $main (param $a i32) - ;;@ index.ts:1:1 - (if - ;;@ index.ts:1:1 - (i32.lt_s - ;;@ index.ts:2:1 - (local.get $a) - ;;@ index.ts:3:1 - (i32.const 0) - ) - ;;@ index.ts:4:1 - (local.set $a - ;;@ index.ts:5:1 - (i32.const 0) - ) - ;;@ index.ts:6:1 - (local.set $a - ;;@ index.ts:7:1 - (i32.const 1) - ) - ) - ;;@ index.ts:8:1 - (call $shouldExcludeFun - (i32.const 1) - (i32.const 2) - ) - ) - (func $shouldExcludeFun (param $a i32)(param $b i32) - ;;@ index.ts:9:1 - (drop - ;;@ index.ts:10:1 - (i32.add - (local.get $a) - (local.get $b) - ) - ) - ) - ;; custom section "sourceMappingURL", size 17 -) \ No newline at end of file diff --git a/tests/cpp/lit/covInstrument/include_exclude.wast.debug.json b/tests/cpp/lit/covInstrument/include_exclude.wast.debug.json deleted file mode 100644 index ec1abbf..0000000 --- a/tests/cpp/lit/covInstrument/include_exclude.wast.debug.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "debugFiles": ["index.ts"], - "debugInfos": { - "main": { - "branchInfo": [ - [0, 1], - [0, 2] - ], - "index": 0, - "lineInfo": [ - [ - [0, 1, 1], - [0, 2, 1], - [0, 3, 1] - ], - [ - [0, 4, 1], - [0, 5, 1] - ], - [ - [0, 6, 1], - [0, 7, 1] - ], - [[0, 8, 1]] - ] - } - } -} diff --git a/tests/cpp/lit/covInstrument/include_exclude.wast.run.log b/tests/cpp/lit/covInstrument/include_exclude.wast.run.log deleted file mode 100644 index a0ba415..0000000 --- a/tests/cpp/lit/covInstrument/include_exclude.wast.run.log +++ /dev/null @@ -1,4 +0,0 @@ -make directly call to function index=0 -basic block entry trace to: function=0, basic block=0 -basic block entry trace to: function=0, basic block=2 -basic block entry trace to: function=0, basic block=3 diff --git a/tests/cpp/utils/utils.cpp b/tests/cpp/utils/utils.cpp index 27ff53a..ce9cec4 100644 --- a/tests/cpp/utils/utils.cpp +++ b/tests/cpp/utils/utils.cpp @@ -1,32 +1,33 @@ #include "utils.h" #include "json/value.h" +#include #include #include namespace testUtils { -bool compareDebugInfoJson(Json::Value &debugInfoJson1, Json::Value &debugInfoJson2) noexcept { - - if ((!debugInfoJson1.isObject()) && (!debugInfoJson2.isObject())) { +bool compareDebugInfoJson(Json::Value const &fixtureJson, + Json::Value const &debugInfoJson) noexcept { + if ((!fixtureJson.isObject()) && (!debugInfoJson.isObject())) { std::cerr << "Not same with types\n"; return false; } // compare debug files - const auto &files1 = debugInfoJson1["debugFiles"]; - const auto &files2 = debugInfoJson2["debugFiles"]; + const auto &files1 = fixtureJson["debugFiles"]; + const auto &files2 = debugInfoJson["debugFiles"]; if (!(files1 == files2)) { std::cerr << "Not same with files\n"; return false; } // compare function debug info - const Json::Value &debugInfos1 = debugInfoJson1["debugInfos"]; - const Json::Value &debugInfos2 = debugInfoJson2["debugInfos"]; + const Json::Value &debugInfos1 = fixtureJson["debugInfos"]; + const Json::Value &debugInfos2 = debugInfoJson["debugInfos"]; const Json::Value::Members &functionNames = debugInfos1.getMemberNames(); const Json::Value::Members &compareFunctionNames = debugInfos2.getMemberNames(); + EXPECT_THAT(functionNames, testing::UnorderedElementsAreArray(compareFunctionNames)); if (functionNames != compareFunctionNames) { - std::cerr << "Not same with function names\n"; return false; } for (const std::string_view functionName : functionNames) { diff --git a/tests/cpp/utils/utils.h b/tests/cpp/utils/utils.h index ddf13d4..35ae82d 100644 --- a/tests/cpp/utils/utils.h +++ b/tests/cpp/utils/utils.h @@ -26,10 +26,9 @@ inline const std::filesystem::path getProjectPath() noexcept { /// /// @brief Compare two debug info json object /// -/// @param debugInfoJson1 -/// @param debugInfoJson2 /// @return Return true if the are same debug info json object -bool compareDebugInfoJson(Json::Value &debugInfoJson1, Json::Value &debugInfoJson2) noexcept; +bool compareDebugInfoJson(Json::Value const &fixtureJson, + Json::Value const &debugInfoJson) noexcept; } // namespace testUtils diff --git a/tests/e2e/assertFailed/stdout.txt b/tests/e2e/assertFailed/stdout.txt index efe8171..40ec26c 100644 --- a/tests/e2e/assertFailed/stdout.txt +++ b/tests/e2e/assertFailed/stdout.txt @@ -11,11 +11,11 @@ Error Message: trace: this test will fail due to an assertion error Reason: abort: this assertion is expected to fail at tests/e2e/assertFailed/assertOnTest.test.ts:5:3 at start:tests/e2e/assertFailed/assertOnTest.test~anonymous|0 (tests/e2e/assertFailed/assertOnTest.test.ts:5:2) - at executeTestFunction (tests/e2e/assertFailed/tmp/assertOnTest.test.instrumented.wasm:1:780) + at executeTestFunction (tests/e2e/assertFailed/tmp/assertOnTest.test.instrumented.wasm:1:791) tests/e2e/assertFailed/tmp/assertOnInit.test - init: Test Crashed! Reason: abort: null at tests/e2e/assertFailed/assertOnInit.test.ts:1:1 at start:tests/e2e/assertFailed/assertOnInit.test (tests/e2e/assertFailed/assertOnInit.test.ts:1:0) - at ~start (tests/e2e/assertFailed/tmp/assertOnInit.test.instrumented.wasm:1:289) + at ~start (tests/e2e/assertFailed/tmp/assertOnInit.test.instrumented.wasm:1:300) diff --git a/tests/e2e/setup-teardown/stdout.txt b/tests/e2e/setup-teardown/stdout.txt index 05761c3..491df06 100644 --- a/tests/e2e/setup-teardown/stdout.txt +++ b/tests/e2e/setup-teardown/stdout.txt @@ -11,6 +11,6 @@ Error Message: Reason: abort: register setup function failed at assembly/implement.ts:20:3 at assembly/implement/beforeEachImpl (assembly/implement.ts:20:2) at assembly/index/beforeEach (assembly/index.ts:47:2) - at start:tests/e2e/setup-teardown/setup_out_of_block.test (tests/e2e/setup-teardown/tmp/setup_out_of_block.test.instrumented.wasm:1:547) - at ~start (tests/e2e/setup-teardown/tmp/setup_out_of_block.test.instrumented.wasm:1:325) + at start:tests/e2e/setup-teardown/setup_out_of_block.test (tests/e2e/setup-teardown/tmp/setup_out_of_block.test.instrumented.wasm:1:558) + at ~start (tests/e2e/setup-teardown/tmp/setup_out_of_block.test.instrumented.wasm:1:336) diff --git a/tests/ts/fixture/src/index.ts b/tests/ts/fixture/src/index.ts new file mode 100644 index 0000000..e69de29 diff --git a/tests/ts/fixture/transformFunction.ts b/tests/ts/fixture/transformFunction.ts deleted file mode 100644 index a1e3bac..0000000 --- a/tests/ts/fixture/transformFunction.ts +++ /dev/null @@ -1,51 +0,0 @@ -export const timerTimeoutCallback = (_userData: i32): void => { - - const result = subscribe((first, second): i32 => { - return first + second; - - }); - - const a = 1 + result; - -} - -let a1 = function(): void {}, a = function(): i32 { return 1; }; - -let a2:() => i32 = () => {return 1}; - -let a3:() => i32 = () => 1; - -@inline -function a4():void { - function b(): void { - - } -} - -declare function a5(): i32; - -export function subscribe(cb: (data: i32, userData: i32) => i32, defaultValue: u32 = 0): i32 { - return cb(100, 200); -} - -subscribe((first, second): i32 => -{ - return second - first; -}); - - -export class Foo { - private static className: string = ""; - private bar: string = ""; - public name: string = "default"; - public historyIndex: u32 = 0; - foo(): void {} - - constructor(_name: string) { - this.name = _name; - } - - static bar(): void { - this.className = "AS-FOO"; - } -} \ No newline at end of file diff --git a/tests/ts/test/core/__snapshots__/precompile.test.ts.snap b/tests/ts/test/core/__snapshots__/precompile.test.ts.snap deleted file mode 100644 index 760ac1b..0000000 --- a/tests/ts/test/core/__snapshots__/precompile.test.ts.snap +++ /dev/null @@ -1,99 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`listFunction transform 1`] = ` -Map { - "tests/ts/fixture/transformFunction.ts" => [ - { - "name": "tests/ts/fixture/transformFunction/Foo.bar", - "range": [ - 49, - 49, - ], - }, - { - "name": "tests/ts/fixture/transformFunction/Foo#constructor", - "range": [ - 45, - 45, - ], - }, - { - "name": "tests/ts/fixture/transformFunction/Foo#foo", - "range": [ - 42, - 42, - ], - }, - { - "name": "", - "range": [ - 33, - 33, - ], - }, - { - "name": "tests/ts/fixture/transformFunction/subscribe", - "range": [ - 28, - 28, - ], - }, - { - "name": "b", - "range": [ - 20, - 22, - ], - }, - { - "name": "tests/ts/fixture/transformFunction/a4", - "range": [ - 20, - 22, - ], - }, - { - "name": "", - "range": [ - 16, - 16, - ], - }, - { - "name": "", - "range": [ - 14, - 14, - ], - }, - { - "name": "", - "range": [ - 12, - 12, - ], - }, - { - "name": "", - "range": [ - 12, - 12, - ], - }, - { - "name": "", - "range": [ - 4, - 4, - ], - }, - { - "name": "", - "range": [ - 3, - 8, - ], - }, - ], -} -`; diff --git a/tests/ts/test/core/analyze.test.ts b/tests/ts/test/core/analyze.test.ts new file mode 100644 index 0000000..d2695fb --- /dev/null +++ b/tests/ts/test/core/analyze.test.ts @@ -0,0 +1,43 @@ +import { analyze } from "../../../../src/core/analyze.js"; + +describe("entry files", () => { + test("specify", () => { + const { entryFiles } = analyze( + { + includes: ["tests/ts/fixture/src"], + excludes: [], + testFiles: undefined, + testNamePattern: null, + entryFiles: ["tests/ts/fixture/src/main.ts"], + }, + [] + ); + expect(entryFiles).toEqual(["tests/ts/fixture/src/main.ts"]); + }); + test("specify empty", () => { + const { entryFiles } = analyze( + { + includes: ["tests/ts/fixture/src"], + excludes: [], + testFiles: undefined, + testNamePattern: null, + entryFiles: [], + }, + [] + ); + expect(entryFiles).toEqual([]); + }); + test("infer", () => { + const { entryFiles } = analyze( + { + includes: ["tests/ts/fixture/src"], + excludes: [], + testFiles: undefined, + testNamePattern: null, + entryFiles: null, + }, + [] + ); + expect(entryFiles).toEqual(["tests/ts/fixture/src/index.ts"]); + }); +}); diff --git a/tests/ts/test/core/instrument.test.ts b/tests/ts/test/core/instrument.test.ts index 6f48e61..a81bec8 100644 --- a/tests/ts/test/core/instrument.test.ts +++ b/tests/ts/test/core/instrument.test.ts @@ -15,11 +15,10 @@ function cleanDirSync(path: string) { test("Instrument", async () => { cleanDirSync(outputDir); - await compile([fixturePath], { outputFolder: outputDir, flags: "--memoryBase 16 --exportTable", isolated: true }); + await compile([fixturePath], [], { outputFolder: outputDir, flags: "--memoryBase 16 --exportTable", isolated: true }); const base = join(outputDir, "constructor").replaceAll(/\\/g, "/"); const wasmPath = base.concat(".wasm"); - const sourceCodePath = "tests/ts/fixture/constructor.ts"; - const results = await instrument([wasmPath], [sourceCodePath], true); + const results = await instrument([wasmPath], true); expect(results.length).toEqual(1); const result = results[0]!; const instrumentedWasm = base.concat(".instrumented.wasm"); diff --git a/tests/ts/test/core/precompile.test.ts b/tests/ts/test/core/precompile.test.ts deleted file mode 100644 index adbc6e2..0000000 --- a/tests/ts/test/core/precompile.test.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { precompile } from "../../../../src/core/precompile.js"; - -test("listFunction transform", async () => { - const unittestPackages = await precompile( - ["tests/ts/fixture/transformFunction.ts"], - [], - undefined, - undefined, - [], - true, - "" - ); - expect(unittestPackages.testCodePaths).toEqual([]); - expect(unittestPackages.sourceFunctions).toMatchSnapshot(); -}); diff --git a/tests/ts/test/core/throwError.test.ts b/tests/ts/test/core/throwError.test.ts index c533e9a..0f4db2d 100644 --- a/tests/ts/test/core/throwError.test.ts +++ b/tests/ts/test/core/throwError.test.ts @@ -1,6 +1,5 @@ // eslint-disable-next-line n/no-extraneous-import import { jest } from "@jest/globals"; -import { precompile } from "../../../../src/core/precompile.js"; import { compile } from "../../../../src/core/compile.js"; import { compiler } from "../../../../src/utils/ascWrapper.js"; @@ -14,14 +13,8 @@ afterEach(() => { jest.clearAllMocks(); }); -test("transform error", async () => { - await expect(async () => { - await precompile(["tests/ts/fixture/transformFunction.ts"], [], undefined, undefined, [], true, ""); - }).rejects.toThrow("mock asc.main() error"); -}); - test("compile error", async () => { await expect(async () => { - await compile(["non-exist.ts"], { outputFolder: "mockFolder", flags: "", isolated: false }); + await compile(["non-exist.ts"], [], { outputFolder: "mockFolder", flags: "", isolated: false }); }).rejects.toThrow("mock asc.main() error"); }); diff --git a/tests/ts/test/parser/__snapshots__/parser.test.ts.snap b/tests/ts/test/parser/__snapshots__/parser.test.ts.snap index bc8fbbf..f753e61 100644 --- a/tests/ts/test/parser/__snapshots__/parser.test.ts.snap +++ b/tests/ts/test/parser/__snapshots__/parser.test.ts.snap @@ -4,17 +4,42 @@ exports[`Parser generateFileCoverage 1`] = ` [ FileCoverageResult { "branchCoverageRate": Rate { - "total": 4, - "used": 4, + "total": 0, + "used": 0, }, - "filename": "test/A.ts", + "filename": [ + "test/A.ts", + [ + { + "name": "", + "range": [ + 39, + 39, + ], + }, + { + "name": "test/A/Foo#check", + "range": [ + 20, + 24, + ], + }, + { + "name": "", + "range": [ + 4, + 12, + ], + }, + ], + ], "functionCoverageRate": Rate { - "total": 3, - "used": 2, + "total": 0, + "used": 0, }, "lineCoverageRate": Rate { - "total": 18, - "used": 9, + "total": 0, + "used": 0, }, "sourceUsedCount": [ CodeCoverage { @@ -31,39 +56,39 @@ exports[`Parser generateFileCoverage 1`] = ` }, CodeCoverage { "source": "", - "usedCount": 0, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 0, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 0, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 0, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 0, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 0, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 0, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 0, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 0, + "usedCount": -1, }, CodeCoverage { "source": "", @@ -79,31 +104,31 @@ exports[`Parser generateFileCoverage 1`] = ` }, CodeCoverage { "source": "", - "usedCount": 3, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 3, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 2, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 2, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 2, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 2, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 1, + "usedCount": -1, }, CodeCoverage { "source": "", @@ -111,7 +136,7 @@ exports[`Parser generateFileCoverage 1`] = ` }, CodeCoverage { "source": "", - "usedCount": 2, + "usedCount": -1, }, CodeCoverage { "source": "", @@ -171,7 +196,7 @@ exports[`Parser generateFileCoverage 1`] = ` }, CodeCoverage { "source": "", - "usedCount": 3, + "usedCount": -1, }, CodeCoverage { "source": "", @@ -223,24 +248,35 @@ exports[`Parser generateFileCoverage 1`] = ` }, ], "statementCoverageRate": Rate { - "total": 18, - "used": 9, + "total": 0, + "used": 0, }, "uncoveredlines": Set {}, }, FileCoverageResult { "branchCoverageRate": Rate { - "total": 1, - "used": 1, + "total": 0, + "used": 0, }, - "filename": "test/B.ts", + "filename": [ + "test/B.ts", + [ + { + "name": "test/B/checkMemory", + "range": [ + 45, + 45, + ], + }, + ], + ], "functionCoverageRate": Rate { - "total": 1, - "used": 1, + "total": 0, + "used": 0, }, "lineCoverageRate": Rate { - "total": 1, - "used": 1, + "total": 0, + "used": 0, }, "sourceUsedCount": [ CodeCoverage { @@ -421,7 +457,7 @@ exports[`Parser generateFileCoverage 1`] = ` }, CodeCoverage { "source": "", - "usedCount": 4, + "usedCount": -1, }, CodeCoverage { "source": "", @@ -449,24 +485,42 @@ exports[`Parser generateFileCoverage 1`] = ` }, ], "statementCoverageRate": Rate { - "total": 1, - "used": 1, + "total": 0, + "used": 0, }, "uncoveredlines": Set {}, }, FileCoverageResult { "branchCoverageRate": Rate { - "total": 4, - "used": 3, + "total": 0, + "used": 0, }, - "filename": "test/C.ts", + "filename": [ + "test/C.ts", + [ + { + "name": "", + "range": [ + 3, + 40, + ], + }, + { + "name": "", + "range": [ + 42, + 44, + ], + }, + ], + ], "functionCoverageRate": Rate { - "total": 2, - "used": 1, + "total": 0, + "used": 0, }, "lineCoverageRate": Rate { - "total": 13, - "used": 9, + "total": 0, + "used": 0, }, "sourceUsedCount": [ CodeCoverage { @@ -507,11 +561,11 @@ exports[`Parser generateFileCoverage 1`] = ` }, CodeCoverage { "source": "", - "usedCount": 2, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 2, + "usedCount": -1, }, CodeCoverage { "source": "", @@ -523,11 +577,11 @@ exports[`Parser generateFileCoverage 1`] = ` }, CodeCoverage { "source": "", - "usedCount": 4, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 2, + "usedCount": -1, }, CodeCoverage { "source": "", @@ -543,7 +597,7 @@ exports[`Parser generateFileCoverage 1`] = ` }, CodeCoverage { "source": "", - "usedCount": 2, + "usedCount": -1, }, CodeCoverage { "source": "", @@ -555,7 +609,7 @@ exports[`Parser generateFileCoverage 1`] = ` }, CodeCoverage { "source": "", - "usedCount": 1, + "usedCount": -1, }, CodeCoverage { "source": "", @@ -563,15 +617,15 @@ exports[`Parser generateFileCoverage 1`] = ` }, CodeCoverage { "source": "", - "usedCount": 1, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 1, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 1, + "usedCount": -1, }, CodeCoverage { "source": "", @@ -583,7 +637,7 @@ exports[`Parser generateFileCoverage 1`] = ` }, CodeCoverage { "source": "", - "usedCount": 0, + "usedCount": -1, }, CodeCoverage { "source": "", @@ -635,15 +689,15 @@ exports[`Parser generateFileCoverage 1`] = ` }, CodeCoverage { "source": "", - "usedCount": 0, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 0, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 0, + "usedCount": -1, }, CodeCoverage { "source": "", @@ -675,26 +729,35 @@ exports[`Parser generateFileCoverage 1`] = ` }, ], "statementCoverageRate": Rate { - "total": 13, - "used": 9, - }, - "uncoveredlines": Set { - 25, + "total": 0, + "used": 0, }, + "uncoveredlines": Set {}, }, FileCoverageResult { "branchCoverageRate": Rate { "total": 0, "used": 0, }, - "filename": "test/D.ts", + "filename": [ + "test/D.ts", + [ + { + "name": "test/D/visit", + "range": [ + 10, + 11, + ], + }, + ], + ], "functionCoverageRate": Rate { - "total": 1, - "used": 1, + "total": 0, + "used": 0, }, "lineCoverageRate": Rate { - "total": 2, - "used": 2, + "total": 0, + "used": 0, }, "sourceUsedCount": [ CodeCoverage { @@ -735,11 +798,11 @@ exports[`Parser generateFileCoverage 1`] = ` }, CodeCoverage { "source": "", - "usedCount": 2, + "usedCount": -1, }, CodeCoverage { "source": "", - "usedCount": 2, + "usedCount": -1, }, CodeCoverage { "source": "", @@ -903,8 +966,8 @@ exports[`Parser generateFileCoverage 1`] = ` }, ], "statementCoverageRate": Rate { - "total": 2, - "used": 2, + "total": 0, + "used": 0, }, "uncoveredlines": Set {}, }, diff --git a/tests/ts/test/parser/singleFileAnalysis.test.ts b/tests/ts/test/parser/singleFileAnalysis.test.ts index c5ab689..4b6a0d0 100644 --- a/tests/ts/test/parser/singleFileAnalysis.test.ts +++ b/tests/ts/test/parser/singleFileAnalysis.test.ts @@ -10,22 +10,6 @@ describe("singleFileAnalysis", () => { expect(analyzer.getResult().functionCoverageRate.toString()).toEqual("0/0"); }); - test("setUnTestedFunction", () => { - const analyzer = new SingleFileCoverageAnalysis("main", source); - analyzer.setTotalFunction(5); - analyzer.setUnTestedFunction([ - [2, 4], - [7, 9], - ]); - const result = analyzer.getResult(); - expect(result.functionCoverageRate.toString()).toEqual("0/5"); - expect(result.lineCoverageRate.toString()).toEqual("0/6"); - expect(result.statementCoverageRate.toString()).toEqual("0/6"); - expect(result.sourceUsedCount.map((count) => count.usedCount)).toEqual([ - -1, 0, 0, 0, -1, -1, 0, 0, 0, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, - ]); - }); - test("merge", () => { const analyzer = new SingleFileCoverageAnalysis("main", source); analyzer.setTotalFunction(5); @@ -59,25 +43,15 @@ describe("singleFileAnalysis", () => { ]), }; analyzer.merge([funcResult_A, funcResult_B]); - analyzer.setUnTestedFunction([ - [2, 4], - [16, 16], - [17, 19], - ]); const result = analyzer.getResult(); expect(result.functionCoverageRate.toString()).toEqual("2/5"); - expect(result.lineCoverageRate.toString()).toEqual("5/14"); - expect(result.statementCoverageRate.toString()).toEqual("5/14"); + expect(result.lineCoverageRate.toString()).toEqual("5/7"); + expect(result.statementCoverageRate.toString()).toEqual("5/7"); expect(result.sourceUsedCount.map((count) => count.usedCount)).toEqual([ - -1, 0, 0, 0, -1, 3, 0, -1, 3, 2, 0, 3, -1, 5, -1, 0, 0, 0, 0, -1, + -1, -1, -1, -1, -1, 3, 0, -1, 3, 2, 0, 3, -1, 5, -1, -1, -1, -1, -1, -1, ]); }); - test("setUnTestedFunction error", () => { - const analyzer = new SingleFileCoverageAnalysis("main", source); - expect(() => analyzer.setUnTestedFunction([[30, 31]])).toThrow("unknown error: There is no 29 Line in file main"); - }); - test("merge error", () => { const analyzer = new SingleFileCoverageAnalysis("main", source); analyzer.setTotalFunction(5); diff --git a/tests/ts/test/utils/utils.test.ts b/tests/ts/test/utils/utils.test.ts index 39ec01b..b6a7887 100644 --- a/tests/ts/test/utils/utils.test.ts +++ b/tests/ts/test/utils/utils.test.ts @@ -2,25 +2,18 @@ import fs from "fs-extra"; import { join } from "node:path"; import { Imports as ASImports } from "@assemblyscript/loader"; import { fileURLToPath, URL } from "node:url"; -import { DebugInfo, CovDebugInfo, ImportFunctionInfo } from "../../../../src/interface.js"; +import { DebugInfo, CovDebugInfo, ImportFunctionInfo, ImportsArgument } from "../../../../src/interface.js"; import { - isIncluded, json2map, - checkFunctionName, + isFunctionInsideFile, checkGenerics, supplyDefaultFunction, + checkVarargs, } from "../../../../src/utils/index.js"; import { Type } from "wasmparser"; const __dirname = fileURLToPath(new URL(".", import.meta.url)); -test("isIncluded", () => { - expect(isIncluded([39, 47], [36, 50])).toEqual(true); - expect(isIncluded([36, 50], [36, 50])).toEqual(true); - expect(isIncluded([33, 38], [36, 50])).toEqual(false); - expect(isIncluded([38, 52], [36, 50])).toEqual(false); -}); - test("json2map", () => { const debugInfoFile = join(__dirname, "..", "..", "fixture", "ifBlock.debugInfo.json"); @@ -59,9 +52,9 @@ test("json2map", () => { expect(debugInfos).toEqual(expectDebugInfos); }); -test("checkFunctionName", () => { - expect(checkFunctionName("source/api.ts", "source/api/myMethod")).toEqual(true); - expect(checkFunctionName("source/api.ts", "start:source/api~anonymous|3~anonymous|1")).toEqual(true); +test("isFunctionInsideFile", () => { + expect(isFunctionInsideFile("source/api.ts", "source/api/myMethod")).toEqual(true); + expect(isFunctionInsideFile("source/api.ts", "start:source/api~anonymous|3~anonymous|1")).toEqual(true); }); test("checkGenerics", () => { @@ -74,6 +67,11 @@ test("checkGenerics", () => { expect(checkGenerics("fun>a")).toEqual(undefined); }); +test("checkVarargs", () => { + expect(checkVarargs("tests/testUtilities/createEmptyVss@varargs")).toEqual("tests/testUtilities/createEmptyVss"); + expect(checkVarargs("tests/testUtilities/createEmptyVss")).toEqual(undefined); +}); + describe("supplyDefaultFunction", () => { test("supplyTest", () => { const mockInfos: ImportFunctionInfo[] = [ @@ -87,7 +85,7 @@ describe("supplyDefaultFunction", () => { env: {}, wasi_snapshot_preview1: {}, }; - supplyDefaultFunction(mockInfos, mockImportObject); + supplyDefaultFunction(mockInfos, mockImportObject, new ImportsArgument({ log: console.log })); expect(typeof mockImportObject["ns"]?.["ut.i32"]).toBe("function"); expect(typeof mockImportObject["ns"]?.["ut.i64"]).toBe("function"); diff --git a/transform/listFunctions.mts b/transform/listFunctions.mts deleted file mode 100644 index d9e892f..0000000 --- a/transform/listFunctions.mts +++ /dev/null @@ -1,544 +0,0 @@ -import { Transform } from "assemblyscript/transform"; -import { - NodeKind, - SourceKind, - CommonFlags, - DeclarationStatement, - DeclaredElement, - Node, - Source, - ParameterNode, - AssertionExpression, - BinaryExpression, - CallExpression, - ClassExpression, - CommaExpression, - ElementAccessExpression, - FunctionExpression, - InstanceOfExpression, - NewExpression, - ParenthesizedExpression, - PropertyAccessExpression, - TernaryExpression, - UnaryPostfixExpression, - UnaryPrefixExpression, - BlockStatement, - DoStatement, - ExpressionStatement, - ForStatement, - ForOfStatement, - IfStatement, - ReturnStatement, - SwitchStatement, - ThrowStatement, - TryStatement, - VariableStatement, - VoidStatement, - WhileStatement, - ClassDeclaration, - EnumDeclaration, - EnumValueDeclaration, - FieldDeclaration, - FunctionDeclaration, - InterfaceDeclaration, - MethodDeclaration, - NamespaceDeclaration, - VariableDeclaration, - SwitchCase, - Program, -} from "assemblyscript"; - -// This interface is same as SourceFunctionInfo in src/interface.ts -// Copy it here to avoid tsc compiling interface.js -interface SourceFunctionInfo { - name: string; - range: [number, number]; -} - -class SourceFunctionTransform extends Transform { - functionInfos: SourceFunctionInfo[] = []; - #elementsByDeclaration: Map = new Map(); - - afterInitialize(program: Program) { - this.#elementsByDeclaration = program.elementsByDeclaration; - // There will be two sources with SourceKind.UserEntry, ~lib/rt/index-incremental.ts should be filtered - program.sources - .filter((source) => source.sourceKind === SourceKind.UserEntry && !source.normalizedPath.startsWith("~lib/")) - .forEach((source) => { - this.functionInfos = []; - this.visitNode(source); - this.functionInfos.reverse(); - const functionInfos = - (globalThis.__functionInfos as Map) || new Map(); - functionInfos.set(source.normalizedPath, this.functionInfos); - globalThis.__functionInfos = functionInfos; - }); - throw new Error("TransformDone"); - } - - visitNode(node: Node) { - // eslint-disable-next-line sonarjs/max-switch-cases - switch (node.kind) { - case NodeKind.Source: { - this.visitSource(node as Source); - break; - } - - // types - case NodeKind.NamedType: - case NodeKind.FunctionType: - case NodeKind.TypeName: - case NodeKind.TypeParameter: { - break; - } - case NodeKind.Parameter: { - this.visitParameterNode(node as ParameterNode); - break; - } - - // Expressions - case NodeKind.Identifier: - case NodeKind.False: - case NodeKind.Literal: - case NodeKind.Null: - case NodeKind.Omitted: - case NodeKind.Super: - case NodeKind.This: - case NodeKind.True: - case NodeKind.Constructor: - case NodeKind.Compiled: { - break; - } - case NodeKind.Assertion: { - this.visitAssertionExpression(node as AssertionExpression); - break; - } - case NodeKind.Binary: { - this.visitBinaryExpression(node as BinaryExpression); - break; - } - case NodeKind.Call: { - this.visitCallExpression(node as CallExpression); - break; - } - case NodeKind.Class: { - this.visitClassExpression(node as ClassExpression); - break; - } - case NodeKind.Comma: { - this.visitCommaExpression(node as CommaExpression); - break; - } - case NodeKind.ElementAccess: { - this.visitElementAccessExpression(node as ElementAccessExpression); - break; - } - case NodeKind.Function: { - this.visitFunctionExpression(node as FunctionExpression); - break; - } - case NodeKind.InstanceOf: { - this.visitInstanceOfExpression(node as InstanceOfExpression); - break; - } - case NodeKind.New: { - this.visitNewExpression(node as NewExpression); - break; - } - case NodeKind.Parenthesized: { - this.visitParenthesizedExpression(node as ParenthesizedExpression); - break; - } - case NodeKind.PropertyAccess: { - this.visitPropertyAccessExpression(node as PropertyAccessExpression); - break; - } - case NodeKind.Ternary: { - this.visitTernaryExpression(node as TernaryExpression); - break; - } - case NodeKind.UnaryPostfix: { - this.visitUnaryPostfixExpression(node as UnaryPostfixExpression); - break; - } - case NodeKind.UnaryPrefix: { - this.visitUnaryPrefixExpression(node as UnaryPrefixExpression); - break; - } - - // statements: - - case NodeKind.Break: - case NodeKind.Empty: - case NodeKind.Export: - case NodeKind.ExportDefault: - case NodeKind.ExportImport: - case NodeKind.Continue: - case NodeKind.Import: - case NodeKind.Module: { - break; - } - case NodeKind.Block: { - this.visitBlockStatement(node as BlockStatement); - break; - } - case NodeKind.Do: { - this.visitDoStatement(node as DoStatement); - break; - } - case NodeKind.Expression: { - this.visitExpressionStatement(node as ExpressionStatement); - break; - } - case NodeKind.For: { - this.visitForStatement(node as ForStatement); - break; - } - case NodeKind.ForOf: { - this.visitForOfStatement(node as ForOfStatement); - break; - } - case NodeKind.If: { - this.visitIfStatement(node as IfStatement); - break; - } - case NodeKind.Return: { - this.visitReturnStatement(node as ReturnStatement); - break; - } - case NodeKind.Switch: { - this.visitSwitchStatement(node as SwitchStatement); - break; - } - case NodeKind.Throw: { - this.visitThrowStatement(node as ThrowStatement); - break; - } - case NodeKind.Try: { - this.visitTryStatement(node as TryStatement); - break; - } - case NodeKind.Variable: { - this.visitVariableStatement(node as VariableStatement); - break; - } - case NodeKind.Void: { - this.visitVoidStatement(node as VoidStatement); - break; - } - case NodeKind.While: { - this.visitWhileStatement(node as WhileStatement); - break; - } - - // declaration statements - case NodeKind.ImportDeclaration: - case NodeKind.TypeDeclaration: { - break; - } - case NodeKind.ClassDeclaration: { - this.visitClassDeclaration(node as ClassDeclaration); - break; - } - case NodeKind.EnumDeclaration: { - this.visitEnumDeclaration(node as EnumDeclaration); - break; - } - case NodeKind.EnumValueDeclaration: { - this.visitEnumValueDeclaration(node as EnumValueDeclaration); - break; - } - case NodeKind.FieldDeclaration: { - this.visitFieldDeclaration(node as FieldDeclaration); - break; - } - case NodeKind.FunctionDeclaration: { - this.visitFunctionDeclaration(node as FunctionDeclaration); - break; - } - case NodeKind.InterfaceDeclaration: { - this.visitInterfaceDeclaration(node as InterfaceDeclaration); - break; - } - case NodeKind.MethodDeclaration: { - this.visitMethodDeclaration(node as MethodDeclaration); - break; - } - case NodeKind.NamespaceDeclaration: { - this.visitNamespaceDeclaration(node as NamespaceDeclaration); - break; - } - case NodeKind.VariableDeclaration: { - this.visitVariableDeclaration(node as VariableDeclaration); - break; - } - - // special - case NodeKind.ExportMember: - case NodeKind.IndexSignature: - case NodeKind.Comment: - case NodeKind.Decorator: { - break; - } - case NodeKind.SwitchCase: { - this.visitSwitchCase(node as SwitchCase); - break; - } - } - } - - visitSource(node: Source) { - for (const statement of node.statements) { - this.visitNode(statement); - } - } - - visitParameterNode(node: ParameterNode) { - if (node.initializer) { - this.visitNode(node.initializer); - } - } - - visitAssertionExpression(node: AssertionExpression) { - this.visitNode(node.expression); - } - - visitBinaryExpression(node: BinaryExpression) { - this.visitNode(node.left); - this.visitNode(node.right); - } - - visitCallExpression(node: CallExpression) { - this.visitNode(node.expression); - for (const arg of node.args) { - this.visitNode(arg); - } - } - - visitClassExpression(node: ClassExpression) { - this.visitClassDeclaration(node.declaration); - } - - visitCommaExpression(node: CommaExpression) { - for (const expr of node.expressions) { - this.visitNode(expr); - } - } - - visitElementAccessExpression(node: ElementAccessExpression) { - this.visitNode(node.expression); - this.visitNode(node.elementExpression); - } - - visitFunctionExpression(node: FunctionExpression) { - this.visitFunctionDeclaration(node.declaration); - } - - visitInstanceOfExpression(node: InstanceOfExpression) { - this.visitNode(node.expression); - } - - visitNewExpression(node: NewExpression) { - for (const arg of node.args) { - this.visitNode(arg); - } - } - - visitParenthesizedExpression(node: ParenthesizedExpression) { - this.visitNode(node.expression); - } - - visitPropertyAccessExpression(node: PropertyAccessExpression) { - this.visitNode(node.expression); - } - - visitTernaryExpression(node: TernaryExpression) { - this.visitNode(node.condition); - this.visitNode(node.ifThen); - this.visitNode(node.ifElse); - } - - visitUnaryPostfixExpression(node: UnaryPostfixExpression) { - this.visitNode(node.operand); - } - - visitUnaryPrefixExpression(node: UnaryPrefixExpression) { - this.visitNode(node.operand); - } - // eslint-disable-next-line sonarjs/no-identical-functions - visitBlockStatement(node: BlockStatement) { - for (const statement of node.statements) { - this.visitNode(statement); - } - } - - visitDoStatement(node: DoStatement) { - this.visitNode(node.body); - this.visitNode(node.condition); - } - - visitExpressionStatement(node: ExpressionStatement) { - this.visitNode(node.expression); - } - - visitForStatement(node: ForStatement) { - if (node.initializer) { - this.visitNode(node.initializer); - } - if (node.condition) { - this.visitNode(node.condition); - } - if (node.incrementor) { - this.visitNode(node.incrementor); - } - this.visitNode(node.body); - } - - visitForOfStatement(node: ForOfStatement) { - this.visitNode(node.variable); - this.visitNode(node.iterable); - this.visitNode(node.body); - } - - visitIfStatement(node: IfStatement) { - this.visitNode(node.condition); - this.visitNode(node.ifTrue); - if (node.ifFalse) { - this.visitNode(node.ifFalse); - } - } - - visitReturnStatement(node: ReturnStatement) { - if (node.value) { - this.visitNode(node.value); - } - } - - visitSwitchStatement(node: SwitchStatement) { - this.visitNode(node.condition); - for (const switchCase of node.cases) { - this.visitSwitchCase(switchCase); - } - } - - visitThrowStatement(node: ThrowStatement) { - this.visitNode(node.value); - } - - visitTryStatement(node: TryStatement) { - for (const stat of node.bodyStatements) { - this.visitNode(stat); - } - if (node.catchStatements) { - for (const stat of node.catchStatements) { - this.visitNode(stat); - } - } - if (node.finallyStatements) { - for (const stat of node.finallyStatements) { - this.visitNode(stat); - } - } - } - - visitVariableStatement(node: VariableStatement) { - for (const declaration of node.declarations) { - this.visitVariableDeclaration(declaration); - } - } - - visitVoidStatement(node: VoidStatement) { - this.visitNode(node.expression); - } - - visitWhileStatement(node: WhileStatement) { - this.visitNode(node.condition); - this.visitNode(node.body); - } - - visitClassDeclaration(node: ClassDeclaration) { - for (const member of node.members) { - this.visitNode(member); - } - } - - visitEnumDeclaration(node: EnumDeclaration) { - for (const value of node.values) { - this.visitEnumValueDeclaration(value); - } - } - // eslint-disable-next-line sonarjs/no-identical-functions - visitEnumValueDeclaration(node: EnumValueDeclaration) { - if (node.initializer) { - this.visitNode(node.initializer); - } - } - // eslint-disable-next-line sonarjs/no-identical-functions - visitFieldDeclaration(node: FieldDeclaration) { - if (node.initializer) { - this.visitNode(node.initializer); - } - } - - visitFunctionDeclaration(node: FunctionDeclaration) { - if (!(node.flags & (CommonFlags.Ambient | CommonFlags.Abstract))) { - let startLine: number, endLine: number; - // startLine is the first Line of Function.body, same as endLine - if (node.body) { - if (node.body.kind === NodeKind.Block && (node.body as BlockStatement).statements.length > 0) { - const bodyStatement = (node.body as BlockStatement).statements; - const startStat = bodyStatement[0]; - startLine = startStat.range.source.lineAt(startStat.range.start); - const endStat = bodyStatement.at(-1); - endLine = endStat.range.source.lineAt(endStat.range.end); - } else { - if (node.flags & CommonFlags.Constructor) { - // do not count constructor without any statements - return; - } - const LineRange = node.body.range; - startLine = LineRange.source.lineAt(LineRange.start); - endLine = LineRange.source.lineAt(LineRange.end); - } - this.functionInfos.push({ - name: this.#elementsByDeclaration.get(node)?.internalName ?? node.name.text, - range: [startLine, endLine], - }); - } - } - if (node.body) { - this.visitNode(node.body); - } - } - - visitInterfaceDeclaration(node: InterfaceDeclaration) { - this.visitClassDeclaration(node); - } - - visitMethodDeclaration(node: MethodDeclaration) { - this.visitFunctionDeclaration(node); - } - // eslint-disable-next-line sonarjs/no-identical-functions - visitNamespaceDeclaration(node: NamespaceDeclaration) { - for (const member of node.members) { - this.visitNode(member); - } - } - // eslint-disable-next-line sonarjs/no-identical-functions - visitVariableDeclaration(node: VariableDeclaration) { - if (node.initializer) { - this.visitNode(node.initializer); - } - } - - visitSwitchCase(node: SwitchCase) { - if (node.label) { - this.visitNode(node.label); - } - for (const stat of node.statements) { - this.visitNode(stat); - } - } -} - -export default SourceFunctionTransform; diff --git a/transform/tsconfig.json b/transform/tsconfig.json deleted file mode 100644 index 6bce599..0000000 --- a/transform/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "module": "ES6", - "target": "ES6", - "moduleResolution": "Node", - "esModuleInterop": true, - "outDir": ".", - "paths": { - "assemblyscript": ["../node_modules/assemblyscript/dist/assemblyscript"], - "assemblyscript/transform": ["../node_modules/assemblyscript/dist/transform"] - } - }, - "files": ["listFunctions.mts"], - "exclude": [] -}