From 92d7ceee017bf774a990dc291b34b3296088c248 Mon Sep 17 00:00:00 2001 From: swapnil <78632212+swapnilpaliwal-sd@users.noreply.github.com> Date: Fri, 9 Oct 2026 10:07:22 -0700 Subject: [PATCH 1/3] fix(parser/js): an import resolves to the code it runs, not a declaration beside it The compiler's resolver prefers a declaration file: require('../') in a package whose package.json names `types` (or an exports "types" condition), and import './x.js' beside a hand-written x.d.ts, both resolved to the .d.ts. JavaScript never loads a declaration, so every value imported that way was external and every call through it an untyped receiver: a test of the package's own API reached nothing in it. When the first answer is a declaration outside node_modules, the import is resolved again with declarations hidden from the resolver (types/typings and the .d.ts substitution fall through to main, the exports code conditions and the .js itself). A module that exists only as a declaration keeps its first answer. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com> --- .../extractors/js-module-edge-extractor.ts | 44 ++++++++++++- .../declaration-beside-the-code/case.json | 62 +++++++++++++++++++ .../declaration-beside-the-code/src/index.js | 3 + .../src/lib/format.d.ts | 1 + .../src/lib/format.js | 2 + .../src/lib/geometry.js | 2 + .../src/lib/reader.js | 5 ++ .../src/lib/shape.d.ts | 1 + .../src/package.json | 2 + .../src/test/format.test.js | 2 + .../src/test/reader.test.js | 5 ++ .../src/test/shape.test.js | 2 + .../src/typings/index.d.ts | 2 + 13 files changed, 132 insertions(+), 1 deletion(-) create mode 100644 tests/cases/javascript/declaration-beside-the-code/case.json create mode 100644 tests/cases/javascript/declaration-beside-the-code/src/index.js create mode 100644 tests/cases/javascript/declaration-beside-the-code/src/lib/format.d.ts create mode 100644 tests/cases/javascript/declaration-beside-the-code/src/lib/format.js create mode 100644 tests/cases/javascript/declaration-beside-the-code/src/lib/geometry.js create mode 100644 tests/cases/javascript/declaration-beside-the-code/src/lib/reader.js create mode 100644 tests/cases/javascript/declaration-beside-the-code/src/lib/shape.d.ts create mode 100644 tests/cases/javascript/declaration-beside-the-code/src/package.json create mode 100644 tests/cases/javascript/declaration-beside-the-code/src/test/format.test.js create mode 100644 tests/cases/javascript/declaration-beside-the-code/src/test/reader.test.js create mode 100644 tests/cases/javascript/declaration-beside-the-code/src/test/shape.test.js create mode 100644 tests/cases/javascript/declaration-beside-the-code/src/typings/index.d.ts diff --git a/parser/src/parsers/javascript/extractors/js-module-edge-extractor.ts b/parser/src/parsers/javascript/extractors/js-module-edge-extractor.ts index 50bd20c4..d07fcb1c 100644 --- a/parser/src/parsers/javascript/extractors/js-module-edge-extractor.ts +++ b/parser/src/parsers/javascript/extractors/js-module-edge-extractor.ts @@ -1214,8 +1214,20 @@ class JsModuleEdgeExtractor { ).resolvedModule?.resolvedFileName; // tsc resolves no `.vue` import itself (its extensions are fixed), so a // component in this program is looked up by path. - const resolved = resolveIn(mode) ?? (mode === ts.ModuleKind.ESNext ? resolveIn(ts.ModuleKind.CommonJS) : undefined) + let resolved = resolveIn(mode) ?? (mode === ts.ModuleKind.ESNext ? resolveIn(ts.ModuleKind.CommonJS) : undefined) ?? resolveVueSpecifier(specifier, this.options.absoluteFilePath, this.options.compilerOptions); + // The compiler prefers a DECLARATION to the code it describes: `require('../')` in a package + // whose package.json names `types: typings/index.d.ts`, or `import './lib.js'` beside a + // hand-written `lib.d.ts`, resolves to the .d.ts. JavaScript never loads a declaration, so + // the module it runs is asked for again with declarations out of sight; left alone, every + // value imported that way was external and each call through it untyped. + if (resolved !== undefined && isDeclarationPath(resolved) + && !resolved.includes(`${path.sep}node_modules${path.sep}`) && !resolved.includes('/node_modules/')) { + const runtime = resolveRuntimeFile(specifier, this.options.absoluteFilePath, this.options.compilerOptions, mode); + if (runtime !== undefined) { + resolved = runtime; + } + } // BOTH SIDES CANONICAL. `projectModuleHashes` is keyed by the files the analyzer // walked from a root it has already resolved through its symlinks; the resolver // answers with the real path for a package found under `node_modules` but does NOT @@ -1280,6 +1292,36 @@ class JsModuleEdgeExtractor { // --------------------------------------------------------------------------- +const DECLARATION_PATH = /\.d\.[cm]?ts$/; + +function isDeclarationPath(file: string): boolean { + return DECLARATION_PATH.test(file); +} + +/** + * The file a JavaScript runtime would load for `specifier`: the compiler's resolution with every + * declaration file hidden from it, so `types`/`typings` and a `.d.ts` beside a `.js` fall through to + * `main`, the `exports` code conditions and the `.js` itself. `undefined` when nothing but a + * declaration answers (a types-only package), which leaves the first answer standing. + */ +function resolveRuntimeFile( + specifier: string, + fromFile: string, + options: ts.CompilerOptions, + mode: ts.ResolutionMode +): string | undefined { + const host: ts.ModuleResolutionHost = { + ...ts.sys, + fileExists: (f) => !isDeclarationPath(f) && ts.sys.fileExists(f), + }; + const answer = (m: ts.ResolutionMode): string | undefined => { + const r = ts.resolveModuleName(specifier, fromFile, { ...options, allowJs: true }, host, undefined, undefined, m) + .resolvedModule?.resolvedFileName; + return r !== undefined && !isDeclarationPath(r) ? r : undefined; + }; + return answer(mode) ?? (mode === ts.ModuleKind.ESNext ? answer(ts.ModuleKind.CommonJS) : undefined); +} + /** `createRequire(import.meta.url)`, however it was imported. */ function isCreateRequireCall(node: ts.CallExpression): boolean { const callee = node.expression; diff --git a/tests/cases/javascript/declaration-beside-the-code/case.json b/tests/cases/javascript/declaration-beside-the-code/case.json new file mode 100644 index 00000000..2257c3b4 --- /dev/null +++ b/tests/cases/javascript/declaration-beside-the-code/case.json @@ -0,0 +1,62 @@ +{ + "lang": "javascript", + "src": "src", + "checks": [ + { + "why": "require('../') in a package whose package.json names a hand-written declaration (types / an exports types condition) loads index.js at runtime: the test that calls the class it exports reaches the method", + "run": [ + "impact", + "Reader.skipBlank", + "--tests" + ], + "want": [ + "reader.test.js", + "tests: 1 of" + ], + "avoid": [ + "tests: 0 of" + ] + }, + { + "why": "require('../lib/format.js') beside a format.d.ts loads the .js: the test's call is an exact edge, not a name match", + "run": [ + "path", + "src/test/format.test.js", + "wrap" + ], + "want": [ + "known_edge" + ], + "avoid": [ + "receiver not typed", + "callee is" + ] + }, + { + "why": "path from the test file is an exact chain through the runtime module, not an unknown receiver", + "run": [ + "path", + "src/test/reader.test.js", + "Reader.readAll" + ], + "want": [ + "known_edge" + ], + "avoid": [ + "receiver not typed" + ] + }, + { + "why": "CONTROL: a module that exists only as a declaration has no code to run; a call through it stays unresolved and is never linked to a same-named project method", + "run": [ + "path", + "src/test/shape.test.js", + "Square.area" + ], + "expect_error": true, + "avoid": [ + "known_edge" + ] + } + ] +} diff --git a/tests/cases/javascript/declaration-beside-the-code/src/index.js b/tests/cases/javascript/declaration-beside-the-code/src/index.js new file mode 100644 index 00000000..5343d15f --- /dev/null +++ b/tests/cases/javascript/declaration-beside-the-code/src/index.js @@ -0,0 +1,3 @@ +const { Reader } = require('./lib/reader.js'); +exports.Reader = Reader; +exports.reader = new Reader(); diff --git a/tests/cases/javascript/declaration-beside-the-code/src/lib/format.d.ts b/tests/cases/javascript/declaration-beside-the-code/src/lib/format.d.ts new file mode 100644 index 00000000..a3746a06 --- /dev/null +++ b/tests/cases/javascript/declaration-beside-the-code/src/lib/format.d.ts @@ -0,0 +1 @@ +export function wrap(text: string): string; diff --git a/tests/cases/javascript/declaration-beside-the-code/src/lib/format.js b/tests/cases/javascript/declaration-beside-the-code/src/lib/format.js new file mode 100644 index 00000000..c2f69520 --- /dev/null +++ b/tests/cases/javascript/declaration-beside-the-code/src/lib/format.js @@ -0,0 +1,2 @@ +function wrap(text) { return `[${text}]`; } +module.exports = { wrap }; diff --git a/tests/cases/javascript/declaration-beside-the-code/src/lib/geometry.js b/tests/cases/javascript/declaration-beside-the-code/src/lib/geometry.js new file mode 100644 index 00000000..a7895a60 --- /dev/null +++ b/tests/cases/javascript/declaration-beside-the-code/src/lib/geometry.js @@ -0,0 +1,2 @@ +class Square { area() { return 4; } } +module.exports = { Square }; diff --git a/tests/cases/javascript/declaration-beside-the-code/src/lib/reader.js b/tests/cases/javascript/declaration-beside-the-code/src/lib/reader.js new file mode 100644 index 00000000..e4e12860 --- /dev/null +++ b/tests/cases/javascript/declaration-beside-the-code/src/lib/reader.js @@ -0,0 +1,5 @@ +class Reader { + readAll(argv) { return this.skipBlank(argv); } + skipBlank(argv) { return argv.length; } +} +exports.Reader = Reader; diff --git a/tests/cases/javascript/declaration-beside-the-code/src/lib/shape.d.ts b/tests/cases/javascript/declaration-beside-the-code/src/lib/shape.d.ts new file mode 100644 index 00000000..68e7e66a --- /dev/null +++ b/tests/cases/javascript/declaration-beside-the-code/src/lib/shape.d.ts @@ -0,0 +1 @@ +export interface Shape { area(): number; } diff --git a/tests/cases/javascript/declaration-beside-the-code/src/package.json b/tests/cases/javascript/declaration-beside-the-code/src/package.json new file mode 100644 index 00000000..572becf2 --- /dev/null +++ b/tests/cases/javascript/declaration-beside-the-code/src/package.json @@ -0,0 +1,2 @@ +{"name": "readkit", "main": "./index.js", "types": "./typings/index.d.ts", + "exports": {".": {"require": {"types": "./typings/index.d.ts", "default": "./index.js"}, "default": "./index.js"}}} diff --git a/tests/cases/javascript/declaration-beside-the-code/src/test/format.test.js b/tests/cases/javascript/declaration-beside-the-code/src/test/format.test.js new file mode 100644 index 00000000..29eecc05 --- /dev/null +++ b/tests/cases/javascript/declaration-beside-the-code/src/test/format.test.js @@ -0,0 +1,2 @@ +const { wrap } = require('../lib/format.js'); +test('wrap', () => { expect(wrap('a')).toBe('[a]'); }); diff --git a/tests/cases/javascript/declaration-beside-the-code/src/test/reader.test.js b/tests/cases/javascript/declaration-beside-the-code/src/test/reader.test.js new file mode 100644 index 00000000..d204209c --- /dev/null +++ b/tests/cases/javascript/declaration-beside-the-code/src/test/reader.test.js @@ -0,0 +1,5 @@ +const kit = require('../'); +test('readAll', () => { + const reader = new kit.Reader(); + expect(() => reader.readAll(['x'])).not.toThrow(); +}); diff --git a/tests/cases/javascript/declaration-beside-the-code/src/test/shape.test.js b/tests/cases/javascript/declaration-beside-the-code/src/test/shape.test.js new file mode 100644 index 00000000..77f0280a --- /dev/null +++ b/tests/cases/javascript/declaration-beside-the-code/src/test/shape.test.js @@ -0,0 +1,2 @@ +const shape = require('../lib/shape'); +test('shape', () => { expect(shape.area()).toBe(1); }); diff --git a/tests/cases/javascript/declaration-beside-the-code/src/typings/index.d.ts b/tests/cases/javascript/declaration-beside-the-code/src/typings/index.d.ts new file mode 100644 index 00000000..b647e452 --- /dev/null +++ b/tests/cases/javascript/declaration-beside-the-code/src/typings/index.d.ts @@ -0,0 +1,2 @@ +export class Reader { readAll(argv: string[]): number; } +export const reader: Reader; From 33885874498d7ec0aad113418c0983ee02ecf40c Mon Sep 17 00:00:00 2001 From: swapnil <78632212+swapnilpaliwal-sd@users.noreply.github.com> Date: Fri, 9 Oct 2026 10:07:22 -0700 Subject: [PATCH 2/3] fix(js): a test file's calls do not decide whether the program's arguments flow The parameter fan cap refuses argument flow into a function called from more than --dispatch-cap places, and it counted every site, tests included. A suite calls the API it tests over and over, so exactly the functions under test went "hot": a constructor called from 21 test files beside 13 library sites lost its parameters, a callback the library handed it and stored on the instance (this.decode = options['decode']) was never followed, and the library's own call through the field resolved only to the fallback beside it. Two verdicts now. Whether the PROGRAM's arguments flow is decided by the program's own sites; whether a TEST's arguments flow is decided by every site, as before, so nothing that flowed before stops flowing: a plugin a test hands the program, which the program calls back with its own class, still types what the test then calls (a version that dropped every test argument lost exactly that; the case guards it). For the name count, the overcount used for module-level functions, a test's bare call or `new` of the name leaves it, while a test's member call that only shares the name (`lib.add(1, 2)` on an export that is a different function, such as a curried closure) stays in it: dropping those too made a curried library's graph three times larger and its index five times slower, for no change in any answer. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com> --- graph/javascript/engine/resolution/fan-cap.dl | 36 +++++++++++- .../engine/resolution/value-flow.dl | 19 +++++-- graph/javascript/souffle/decls_all.dl | 8 +++ .../tests-do-not-make-the-api-hot/case.json | 55 +++++++++++++++++++ .../src/lib/codec.js | 7 +++ .../src/lib/codecs/alpha.js | 4 ++ .../src/lib/codecs/beta.js | 4 ++ .../src/lib/codecs/gamma.js | 4 ++ .../src/lib/core.js | 5 ++ .../src/lib/each.js | 3 + .../src/lib/loader.js | 10 ++++ .../src/lib/plugins/shout.js | 4 ++ .../src/lib/walk.js | 29 ++++++++++ .../src/package.json | 1 + .../src/test/custom.test.js | 25 +++++++++ .../src/test/load.test.js | 2 + .../src/test/plugin.test.js | 4 ++ .../src/test/walk.test.js | 2 + 18 files changed, 217 insertions(+), 5 deletions(-) create mode 100644 tests/cases/javascript/tests-do-not-make-the-api-hot/case.json create mode 100644 tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/codec.js create mode 100644 tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/codecs/alpha.js create mode 100644 tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/codecs/beta.js create mode 100644 tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/codecs/gamma.js create mode 100644 tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/core.js create mode 100644 tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/each.js create mode 100644 tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/loader.js create mode 100644 tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/plugins/shout.js create mode 100644 tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/walk.js create mode 100644 tests/cases/javascript/tests-do-not-make-the-api-hot/src/package.json create mode 100644 tests/cases/javascript/tests-do-not-make-the-api-hot/src/test/custom.test.js create mode 100644 tests/cases/javascript/tests-do-not-make-the-api-hot/src/test/load.test.js create mode 100644 tests/cases/javascript/tests-do-not-make-the-api-hot/src/test/plugin.test.js create mode 100644 tests/cases/javascript/tests-do-not-make-the-api-hot/src/test/walk.test.js diff --git a/graph/javascript/engine/resolution/fan-cap.dl b/graph/javascript/engine/resolution/fan-cap.dl index af6bdd93..ad69acdb 100644 --- a/graph/javascript/engine/resolution/fan-cap.dl +++ b/graph/javascript/engine/resolution/fan-cap.dl @@ -80,14 +80,48 @@ direct_export_func(mod, n, m) :- export_decl(_, n, _, _, _, _, _, se, mod, _), s // shares its name with hundreds of unrelated sites and must not be capped by them, // while a module-level `each` or `merge` called under its own name from everywhere // is exactly the generic utility the cap exists for. +// +// The PROGRAM's arguments are decided by the program's sites alone. A suite calls the +// API it tests over and over, and counted with the program those calls made exactly the +// functions under test "generic utilities": their parameters went untracked, so a +// callback the program hands a constructor and stores on the instance (`this.decode = +// options.decode`) was lost, and the program's own call through that field resolved only +// to the fallback beside it. So there are two verdicts: +// method_is_hot every site counts (as before): decides whether a TEST's +// arguments flow in +// method_is_hot_in_program test files' sites do not count: decides whether the +// PROGRAM's arguments flow in +// Nothing that flowed before stops flowing (a test handing the program a plugin it calls +// back still reaches it). A function hot only because of its tests now receives what the +// program passes it and still nothing from the tests: their callbacks would be the widest +// values it holds, and with them the call through the stored field would be over the +// dispatch cap and answer nothing. param_flow_capped_at(n) :- dispatch_cap(s), n = to_number(s). direct_call_count(m, n) :- direct_call_site(_, m), n = count : { direct_call_site(_, m) }. method_is_hot(m, n) :- direct_call_count(m, n), param_flow_capped_at(c), n > c. name_call_count(cn, n) :- call_site(_, _, cn, _, _, _, _, _, _), cn != "", n = count : { call_site(_, _, cn, _, _, _, _, _, _) }. +program_direct_site(ce, m) :- direct_call_site(ce, m), !site_in_test_module(ce). +program_direct_count(m, n) :- program_direct_site(_, m), n = count : { program_direct_site(_, m) }. +method_is_hot_in_program(m, n) :- program_direct_count(m, n), param_flow_capped_at(c), n > c. +// The NAME count is the overcount for module-level functions. A test's member call that +// only shares the name (`lib.add(1, 2)` on an export that is a different function, a +// curried closure) stays in it, as before; a test's bare call or `new` of the name +// (`build(...)`, `new lib.Codec(...)`) denotes the function itself and leaves it. +test_names_itself(ce) :- site_in_test_module(ce), call_site(_, ck, _, _, _, _, ce, _, _), call_kind_is_callee_form(ck). +test_names_itself(ce) :- site_in_test_module(ce), call_site(_, "CONSTRUCTOR_CALL", _, _, _, _, ce, _, _). +program_name_site(m, ce) :- module_level_function(m, cn), call_site(_, _, cn, _, _, _, ce, _, _), !test_names_itself(ce). +program_name_count(m, n) :- program_name_site(m, _), n = count : { program_name_site(m, _) }. +// ── test_module / site_in_test_module ────────────── +// By path, as runners find their files: a `test`/`tests`/`__tests__`/`spec(s)` directory, +// or a `.test.` / `.spec.` file. +test_module(mod) :- module_file("client", fp, mod), + match("(.*/)?(tests?|__tests__|specs?)/.*|.*[.](test|spec)[.][cm]?[jt]sx?", fp). +site_in_test_module(ce) :- expr_owner(_, _, mod, ce), test_module(mod). module_level_function(m, n) :- method_decl(_, n, "FUNCTION_DECLARATION", _, _, "", mod, m), method_scopes(_, _, _, init, m), module_init(_, init, mod). module_level_function(m, n) :- var_decl(_, n, _, _, _, mod, v), function_binding_value(v, m), method_decl(_, _, _, _, _, "", mod, m), method_scopes(_, _, _, init, m), module_init(_, init, mod). method_is_hot(m, n) :- module_level_function(m, cn), name_call_count(cn, n), param_flow_capped_at(c), n > c. +method_is_hot_in_program(m, n) :- program_name_count(m, n), param_flow_capped_at(c), n > c. // ── param_fan_capped(ParamHash, SiteCount) — countable ────────────────────── -param_fan_capped(p, n) :- method_is_hot(m, n), param_decl(_, _, _, m, p). +param_fan_capped(p, n) :- method_is_hot_in_program(m, n), param_decl(_, _, _, m, p). diff --git a/graph/javascript/engine/resolution/value-flow.dl b/graph/javascript/engine/resolution/value-flow.dl index 2c79e52b..e78863e2 100644 --- a/graph/javascript/engine/resolution/value-flow.dl +++ b/graph/javascript/engine/resolution/value-flow.dl @@ -264,8 +264,15 @@ param_value(p, "inst", t) :- param_type_name(_, _, tr, p), type_ref_type(tr, t). // values on every export, a 20-minute solve — because a generic library is exactly // the shape whose parameters unify everything. What a library does with a callback it // is handed is a MODEL (resolution/frameworks.dl), stated per library, not inferred. -param_value(p, k, i) :- call_arg(ce, pos, arg), expr_resolves_to_method(ce, m), !method_is_hot(m, _), - method_decl("client", _, _, _, _, _, _, m), +// The program's sites pass their arguments unless the PROGRAM calls the function from +// more places than the cap; a test file's sites only while every site together stays +// under it (fan-cap.dl). +param_value(p, k, i) :- call_arg(ce, pos, arg), expr_resolves_to_method(ce, m), !site_in_test_module(ce), + !method_is_hot_in_program(m, _), method_decl("client", _, _, _, _, _, _, m), + param_decl(_, _, ppos, m, p), to_number(ppos) = pos, !param_is_rest(_, p), + expr_value(arg, k, i). +param_value(p, k, i) :- call_arg(ce, pos, arg), expr_resolves_to_method(ce, m), site_in_test_module(ce), + !method_is_hot(m, _), method_decl("client", _, _, _, _, _, _, m), param_decl(_, _, ppos, m, p), to_number(ppos) = pos, !param_is_rest(_, p), expr_value(arg, k, i). // THE ONE LIBRARY EXCEPTION: a UMD wrapper (#710). `(function (root, factory) { @@ -283,8 +290,12 @@ param_value(p, "func", f) :- call_site(_, "IIFE_CALL", _, _, _, _, ce, _, _), ca expr_value(arg, "func", f). // A rest parameter IS an array; the arguments at and past its position are its elements. param_value(p, "arr", p) :- param_is_rest(_, p). -elem_value(p, k, i) :- call_arg(ce, pos, arg), expr_resolves_to_method(ce, m), !method_is_hot(m, _), - method_decl("client", _, _, _, _, _, _, m), +elem_value(p, k, i) :- call_arg(ce, pos, arg), expr_resolves_to_method(ce, m), !site_in_test_module(ce), + !method_is_hot_in_program(m, _), method_decl("client", _, _, _, _, _, _, m), + param_decl(_, _, ppos, m, p), param_is_rest(_, p), to_number(ppos) <= pos, + expr_value(arg, k, i). +elem_value(p, k, i) :- call_arg(ce, pos, arg), expr_resolves_to_method(ce, m), site_in_test_module(ce), + !method_is_hot(m, _), method_decl("client", _, _, _, _, _, _, m), param_decl(_, _, ppos, m, p), param_is_rest(_, p), to_number(ppos) <= pos, expr_value(arg, k, i). // A default value: `cb = () => {}`. The parser gives the parameter the default's diff --git a/graph/javascript/souffle/decls_all.dl b/graph/javascript/souffle/decls_all.dl index b7b56647..e30d8c1f 100644 --- a/graph/javascript/souffle/decls_all.dl +++ b/graph/javascript/souffle/decls_all.dl @@ -462,6 +462,14 @@ .decl param_fan_capped(c0:symbol, c1:number) .decl import_binding_is_default_like(c0:symbol) .decl name_call_count(c0:symbol, c1:number) +.decl test_module(c0:symbol) +.decl site_in_test_module(c0:symbol) +.decl program_direct_site(c0:symbol, c1:symbol) +.decl program_direct_count(c0:symbol, c1:number) +.decl method_is_hot_in_program(c0:symbol, c1:number) +.decl test_names_itself(c0:symbol) +.decl program_name_site(c0:symbol, c1:symbol) +.decl program_name_count(c0:symbol, c1:number) .decl module_level_function(c0:symbol, c1:symbol) .decl ambient_collection(c0:symbol) .decl coll_method_call(c0:symbol, c1:symbol, c2:symbol) diff --git a/tests/cases/javascript/tests-do-not-make-the-api-hot/case.json b/tests/cases/javascript/tests-do-not-make-the-api-hot/case.json new file mode 100644 index 00000000..cd47315d --- /dev/null +++ b/tests/cases/javascript/tests-do-not-make-the-api-hot/case.json @@ -0,0 +1,55 @@ +{ + "lang": "javascript", + "src": "src", + "checks": [ + { + "why": "a constructor the tests call 24 times is not a generic utility: the program's own `new Codec(name, {decode})` reaches the parameter, so `codec.decode()` in the loader calls the stored callback and the loader's test reaches it", + "run": [ + "impact", + "decode_alpha", + "--tests" + ], + "want": [ + "load", + "load.test.js" + ], + "avoid": [ + "tests: 0 of" + ] + }, + { + "why": "a test's argument does not flow into the program's parameter: the loader's call is one of the three library callbacks and the fallback, never a test's own decoder", + "run": [ + "impact", + "customDecode3" + ], + "avoid": [ + "load " + ] + }, + { + "why": "CONTROL: a utility the PROGRAM calls from 23 places stays capped: what it is handed is not followed, so the callback is not a callee of each", + "run": [ + "impact", + "onItem" + ], + "avoid": [ + "each " + ] + }, + { + "why": "a test still hands the program what it calls back: a plugin passed from a test to core.extend is called with the class, so the method it adds is what the test's `core().shout()` calls (what flowed before keeps flowing)", + "run": [ + "path", + "src/test/plugin.test.js", + "shout" + ], + "want": [ + "known_edge" + ], + "avoid": [ + "receiver not typed" + ] + } + ] +} diff --git a/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/codec.js b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/codec.js new file mode 100644 index 00000000..cbf366d7 --- /dev/null +++ b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/codec.js @@ -0,0 +1,7 @@ +'use strict'; +function Codec(name, options) { + options = options || {}; + this.name = name; + this.decode = options['decode'] || function () { return true; }; +} +module.exports = Codec; diff --git a/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/codecs/alpha.js b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/codecs/alpha.js new file mode 100644 index 00000000..a5864f2e --- /dev/null +++ b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/codecs/alpha.js @@ -0,0 +1,4 @@ +'use strict'; +var Codec = require('../codec'); +function decode_alpha(data) { return data === 'alpha'; } +module.exports = new Codec('codec:alpha', { decode: decode_alpha }); diff --git a/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/codecs/beta.js b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/codecs/beta.js new file mode 100644 index 00000000..8b97967e --- /dev/null +++ b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/codecs/beta.js @@ -0,0 +1,4 @@ +'use strict'; +var Codec = require('../codec'); +function decode_beta(data) { return data === 'beta'; } +module.exports = new Codec('codec:beta', { decode: decode_beta }); diff --git a/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/codecs/gamma.js b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/codecs/gamma.js new file mode 100644 index 00000000..e1d9291d --- /dev/null +++ b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/codecs/gamma.js @@ -0,0 +1,4 @@ +'use strict'; +var Codec = require('../codec'); +function decode_gamma(data) { return data === 'gamma'; } +module.exports = new Codec('codec:gamma', { decode: decode_gamma }); diff --git a/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/core.js b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/core.js new file mode 100644 index 00000000..8424af35 --- /dev/null +++ b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/core.js @@ -0,0 +1,5 @@ +'use strict'; +class Core {} +function core() { return new Core(); } +core.extend = (plugin) => { plugin(Core); return core; }; +module.exports = core; diff --git a/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/each.js b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/each.js new file mode 100644 index 00000000..94936ac2 --- /dev/null +++ b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/each.js @@ -0,0 +1,3 @@ +'use strict'; +function each(list, fn) { for (var i = 0; i < list.length; i++) fn(list[i]); } +module.exports = each; diff --git a/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/loader.js b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/loader.js new file mode 100644 index 00000000..2086d532 --- /dev/null +++ b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/loader.js @@ -0,0 +1,10 @@ +'use strict'; +var codecs = [require('./codecs/alpha'), require('./codecs/beta'), require('./codecs/gamma')]; +function load(x) { + for (var i = 0; i < codecs.length; i++) { + var codec = codecs[i]; + if (codec.decode(x)) return codec.name; + } + return null; +} +module.exports = { load, Codec: require('./codec') }; diff --git a/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/plugins/shout.js b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/plugins/shout.js new file mode 100644 index 00000000..21df4a2e --- /dev/null +++ b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/plugins/shout.js @@ -0,0 +1,4 @@ +'use strict'; +module.exports = (C) => { + C.prototype.shout = function shout() { return 'HEY'; }; +}; diff --git a/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/walk.js b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/walk.js new file mode 100644 index 00000000..bee2917a --- /dev/null +++ b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/lib/walk.js @@ -0,0 +1,29 @@ +'use strict'; +var each = require('./each'); +function onItem(x) { return x; } +function walk(list) { + each(list, function step0(x) { return x + 0; }); + each(list, function step1(x) { return x + 1; }); + each(list, function step2(x) { return x + 2; }); + each(list, function step3(x) { return x + 3; }); + each(list, function step4(x) { return x + 4; }); + each(list, function step5(x) { return x + 5; }); + each(list, function step6(x) { return x + 6; }); + each(list, function step7(x) { return x + 7; }); + each(list, function step8(x) { return x + 8; }); + each(list, function step9(x) { return x + 9; }); + each(list, function step10(x) { return x + 10; }); + each(list, function step11(x) { return x + 11; }); + each(list, function step12(x) { return x + 12; }); + each(list, function step13(x) { return x + 13; }); + each(list, function step14(x) { return x + 14; }); + each(list, function step15(x) { return x + 15; }); + each(list, function step16(x) { return x + 16; }); + each(list, function step17(x) { return x + 17; }); + each(list, function step18(x) { return x + 18; }); + each(list, function step19(x) { return x + 19; }); + each(list, function step20(x) { return x + 20; }); + each(list, function step21(x) { return x + 21; }); + each(list, onItem); +} +module.exports = { walk, onItem }; diff --git a/tests/cases/javascript/tests-do-not-make-the-api-hot/src/package.json b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/package.json new file mode 100644 index 00000000..8b109c46 --- /dev/null +++ b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/package.json @@ -0,0 +1 @@ +{"name": "codeckit"} diff --git a/tests/cases/javascript/tests-do-not-make-the-api-hot/src/test/custom.test.js b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/test/custom.test.js new file mode 100644 index 00000000..d64224b8 --- /dev/null +++ b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/test/custom.test.js @@ -0,0 +1,25 @@ +const { Codec } = require('../lib/loader'); +test('custom 0', () => { const t = new Codec('!c0', { decode: function customDecode0(d) { return d === 0; } }); expect(t.decode(0)).toBe(true); }); +test('custom 1', () => { const t = new Codec('!c1', { decode: function customDecode1(d) { return d === 1; } }); expect(t.decode(1)).toBe(true); }); +test('custom 2', () => { const t = new Codec('!c2', { decode: function customDecode2(d) { return d === 2; } }); expect(t.decode(2)).toBe(true); }); +test('custom 3', () => { const t = new Codec('!c3', { decode: function customDecode3(d) { return d === 3; } }); expect(t.decode(3)).toBe(true); }); +test('custom 4', () => { const t = new Codec('!c4', { decode: function customDecode4(d) { return d === 4; } }); expect(t.decode(4)).toBe(true); }); +test('custom 5', () => { const t = new Codec('!c5', { decode: function customDecode5(d) { return d === 5; } }); expect(t.decode(5)).toBe(true); }); +test('custom 6', () => { const t = new Codec('!c6', { decode: function customDecode6(d) { return d === 6; } }); expect(t.decode(6)).toBe(true); }); +test('custom 7', () => { const t = new Codec('!c7', { decode: function customDecode7(d) { return d === 7; } }); expect(t.decode(7)).toBe(true); }); +test('custom 8', () => { const t = new Codec('!c8', { decode: function customDecode8(d) { return d === 8; } }); expect(t.decode(8)).toBe(true); }); +test('custom 9', () => { const t = new Codec('!c9', { decode: function customDecode9(d) { return d === 9; } }); expect(t.decode(9)).toBe(true); }); +test('custom 10', () => { const t = new Codec('!c10', { decode: function customDecode10(d) { return d === 10; } }); expect(t.decode(10)).toBe(true); }); +test('custom 11', () => { const t = new Codec('!c11', { decode: function customDecode11(d) { return d === 11; } }); expect(t.decode(11)).toBe(true); }); +test('custom 12', () => { const t = new Codec('!c12', { decode: function customDecode12(d) { return d === 12; } }); expect(t.decode(12)).toBe(true); }); +test('custom 13', () => { const t = new Codec('!c13', { decode: function customDecode13(d) { return d === 13; } }); expect(t.decode(13)).toBe(true); }); +test('custom 14', () => { const t = new Codec('!c14', { decode: function customDecode14(d) { return d === 14; } }); expect(t.decode(14)).toBe(true); }); +test('custom 15', () => { const t = new Codec('!c15', { decode: function customDecode15(d) { return d === 15; } }); expect(t.decode(15)).toBe(true); }); +test('custom 16', () => { const t = new Codec('!c16', { decode: function customDecode16(d) { return d === 16; } }); expect(t.decode(16)).toBe(true); }); +test('custom 17', () => { const t = new Codec('!c17', { decode: function customDecode17(d) { return d === 17; } }); expect(t.decode(17)).toBe(true); }); +test('custom 18', () => { const t = new Codec('!c18', { decode: function customDecode18(d) { return d === 18; } }); expect(t.decode(18)).toBe(true); }); +test('custom 19', () => { const t = new Codec('!c19', { decode: function customDecode19(d) { return d === 19; } }); expect(t.decode(19)).toBe(true); }); +test('custom 20', () => { const t = new Codec('!c20', { decode: function customDecode20(d) { return d === 20; } }); expect(t.decode(20)).toBe(true); }); +test('custom 21', () => { const t = new Codec('!c21', { decode: function customDecode21(d) { return d === 21; } }); expect(t.decode(21)).toBe(true); }); +test('custom 22', () => { const t = new Codec('!c22', { decode: function customDecode22(d) { return d === 22; } }); expect(t.decode(22)).toBe(true); }); +test('custom 23', () => { const t = new Codec('!c23', { decode: function customDecode23(d) { return d === 23; } }); expect(t.decode(23)).toBe(true); }); diff --git a/tests/cases/javascript/tests-do-not-make-the-api-hot/src/test/load.test.js b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/test/load.test.js new file mode 100644 index 00000000..9cffc0a6 --- /dev/null +++ b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/test/load.test.js @@ -0,0 +1,2 @@ +const { load } = require('../lib/loader'); +test('alpha', () => { expect(load('alpha')).toBe('codec:alpha'); }); diff --git a/tests/cases/javascript/tests-do-not-make-the-api-hot/src/test/plugin.test.js b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/test/plugin.test.js new file mode 100644 index 00000000..f4478092 --- /dev/null +++ b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/test/plugin.test.js @@ -0,0 +1,4 @@ +const core = require('../lib/core'); +const shoutPlugin = require('../lib/plugins/shout'); +core.extend(shoutPlugin); +test('shout', () => { expect(core().shout()).toBe('HEY'); }); diff --git a/tests/cases/javascript/tests-do-not-make-the-api-hot/src/test/walk.test.js b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/test/walk.test.js new file mode 100644 index 00000000..4234f11f --- /dev/null +++ b/tests/cases/javascript/tests-do-not-make-the-api-hot/src/test/walk.test.js @@ -0,0 +1,2 @@ +const { walk } = require('../lib/walk'); +test('walk', () => { walk([1]); }); From 408d01f3102538b611c059ba4241fef8f47a4da1 Mon Sep 17 00:00:00 2001 From: swapnil <78632212+swapnilpaliwal-sd@users.noreply.github.com> Date: Fri, 9 Oct 2026 10:07:22 -0700 Subject: [PATCH 3/3] fix(js): a call of what a project wrapper returned runs what that site was handed `export default promisable(eachItem, 3)`, `const add = curry2(function add(a, b) {...})`: the wrapper returns a closure that calls its parameter, and the module exports that closure. The closure's call of the parameter is read context-insensitively, so it held every function any of the wrapper's sites passed (past the fan cap: nothing), and a test calling the export reached the closure and stopped there. The wrapping site is already a value of its own (("wrap", site), value-flow.dl), used for what a call through it returns. A call through it now also runs the function that site was handed at the forwarded position, what the wrapper writes into that parameter, or what a wrapped argument itself runs: one of a set, beside the edge to the closure the call keeps, and under the dispatch cap (a parameter every wrapped export is passed to runs one of them, and naming all of them says nothing). A wrapper whose closure never calls its parameter is not a wrapper and runs nothing it was handed. Two engine goldens gain these edges (memoize / once / lazy results and the param-reassigned shapes), reviewed and blessed with --oracle. Co-authored-by: axiomcode-bot[bot] <334110751+axiomcode-bot[bot]@users.noreply.github.com> --- .../engine/call-edge-generation/calls.dl | 23 +++++++++++++++++++ graph/javascript/souffle/decls_all.dl | 4 ++++ .../expected/64-memoize-wrapper-result.edges | 19 +++++++++++++++ .../65-wrapper-param-reassigned.edges | 15 ++++++++++++ .../case.json | 16 +++++++++++++ .../src/lib/index.js | 3 +++ .../src/lib/label.js | 3 +++ .../src/lib/named.js | 3 +++ .../src/lib/op0.js | 3 +++ .../src/lib/op1.js | 3 +++ .../src/lib/op10.js | 3 +++ .../src/lib/op11.js | 3 +++ .../src/lib/op12.js | 3 +++ .../src/lib/op13.js | 3 +++ .../src/lib/op14.js | 3 +++ .../src/lib/op15.js | 3 +++ .../src/lib/op16.js | 3 +++ .../src/lib/op17.js | 3 +++ .../src/lib/op18.js | 3 +++ .../src/lib/op19.js | 3 +++ .../src/lib/op2.js | 3 +++ .../src/lib/op20.js | 3 +++ .../src/lib/op21.js | 3 +++ .../src/lib/op22.js | 3 +++ .../src/lib/op23.js | 3 +++ .../src/lib/op3.js | 3 +++ .../src/lib/op4.js | 3 +++ .../src/lib/op5.js | 3 +++ .../src/lib/op6.js | 3 +++ .../src/lib/op7.js | 3 +++ .../src/lib/op8.js | 3 +++ .../src/lib/op9.js | 3 +++ .../src/lib/promisable.js | 12 ++++++++++ .../src/package.json | 1 + .../src/test/named.test.js | 2 ++ .../src/test/op3.test.js | 2 ++ .../src/test/op4.test.js | 2 ++ 37 files changed, 177 insertions(+) create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/case.json create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/index.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/label.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/named.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op0.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op1.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op10.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op11.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op12.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op13.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op14.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op15.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op16.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op17.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op18.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op19.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op2.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op20.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op21.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op22.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op23.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op3.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op4.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op5.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op6.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op7.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op8.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op9.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/promisable.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/package.json create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/test/named.test.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/test/op3.test.js create mode 100644 tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/test/op4.test.js diff --git a/graph/javascript/engine/call-edge-generation/calls.dl b/graph/javascript/engine/call-edge-generation/calls.dl index 39dbe789..5745b29d 100644 --- a/graph/javascript/engine/call-edge-generation/calls.dl +++ b/graph/javascript/engine/call-edge-generation/calls.dl @@ -170,6 +170,29 @@ call_chain_edge(ce, caller, "-", m, "client", "multi_inferred", kind) :- wrapper_call_target(ce, m), !call_over_cap(ce), call_from(ce, caller), invocation_site(ce, kind). variable_reassigned(v) :- expr_kind(_, "ASSIGNMENT", _, a), expr_child(_, a, "ASSIGNMENT_TARGET", _, tgt), expr_binding(_, v, tgt). +// ── a call of what a PROJECT wrapper returned runs what that site was handed ── +// `export default promisable(eachItem, 3)`, `const add = curry2(function add(a, b) {…})`: +// the wrapper returns a closure that calls its parameter, and every export of the module +// is that closure. The closure's call of the parameter is read context-insensitively, so +// it held every function any of the wrapper's sites passed (past the fan cap: nothing), +// and a test calling the export reached the closure and stopped there. The site is +// already a value of its own, ("wrap", site), beside the closure (value-flow.dl), and a +// call through it takes its RESULT from that site's argument alone; the same site says +// what the call RUNS: the function it was handed at the forwarded position, what the +// wrapper writes into that parameter, or what a wrapped argument itself runs +// (`memoize(once(f))`). One of a set, beside the edge to the closure the call keeps, and +// under the same fan cap: a callee that may hold more wrapped values than --dispatch-cap +// (a parameter every wrapped export is passed to) runs one of them, and naming all of +// them says nothing. +wrap_runs(s, g) :- wrap_site_arg(s, _, arg), expr_value(arg, "func", g). +wrap_runs(s, g) :- wrap_site_arg(s, _, arg), expr_value(arg, "wrap", s2), wrap_runs(s2, g). +wrap_runs(s, g) :- wrap_site(s, h, pos), wrapper_param_written(h, pos, o), expr_value(o, "func", g). +wrapped_call_target(ce, g) :- callee_value(ce, "wrap", s), wrap_runs(s, g), method_prov(g, "client"), + !call_site(_, "FUNCTION_CALL_BIND", _, _, _, _, ce, _, _). +wrapped_target_count(ce, n) :- wrapped_call_target(ce, _), n = count : { wrapped_call_target(ce, _) }. +wrapped_over_cap(ce) :- wrapped_target_count(ce, n), dispatch_cap(c), n > to_number(c). +call_chain_edge(ce, caller, "-", g, "client", "multi_inferred", kind) :- + wrapped_call_target(ce, g), !call_over_cap(ce), !wrapped_over_cap(ce), call_from(ce, caller), invocation_site(ce, kind). // `(c ? a : b)()`, `(0, cb)()`, `make()()`: the callee is computed by an expression. // `new Function(s)()` / `Function(s)()` runs code built at run time. unresolved_value_callee(ce, "dynamic_code") :- value_callee_unresolved(ce, c), callee_builds_code(c). diff --git a/graph/javascript/souffle/decls_all.dl b/graph/javascript/souffle/decls_all.dl index e30d8c1f..bc489cb6 100644 --- a/graph/javascript/souffle/decls_all.dl +++ b/graph/javascript/souffle/decls_all.dl @@ -112,6 +112,10 @@ .decl wrapper_holder_call(c0:symbol, c1:symbol) .decl wrapper_runs(c0:symbol, c1:symbol) .decl wrapper_call_target(c0:symbol, c1:symbol) +.decl wrap_runs(c0:symbol, c1:symbol) +.decl wrapped_call_target(c0:symbol, c1:symbol) +.decl wrapped_target_count(c0:symbol, c1:number) +.decl wrapped_over_cap(c0:symbol) .decl live_export_variable(c0:symbol, c1:symbol) .decl this_type_open(c0:symbol) diff --git a/graph/test/javascript/expected/64-memoize-wrapper-result.edges b/graph/test/javascript/expected/64-memoize-wrapper-result.edges index c3d46e25..46a76a11 100644 --- a/graph/test/javascript/expected/64-memoize-wrapper-result.edges +++ b/graph/test/javascript/expected/64-memoize-wrapper-result.edges @@ -1,6 +1,8 @@ app.js:11:3 FUNCTION_CALL getAlpha -> known_edge memoize.js:5:10 +app.js:11:3 FUNCTION_CALL getAlpha -> multi_inferred app.js:7:26 app.js:11:3 METHOD_CALL getAlpha().getHooks -> known_edge alpha.js:2:3 getHooks app.js:12:3 FUNCTION_CALL getBeta -> known_edge memoize.js:5:10 +app.js:12:3 FUNCTION_CALL getBeta -> multi_inferred app.js:8:25 app.js:12:3 METHOD_CALL getBeta().getHooks -> known_edge beta.js:2:3 getHooks app.js:16:20 FUNCTION_CALL once -> callback_registered app.js:16:25 app.js:16:20 FUNCTION_CALL once -> known_edge once.js:2:1 once @@ -15,12 +17,16 @@ app.js:19:19 FUNCTION_CALL lazy -> callback_registered app.js:19:24 app.js:19:19 FUNCTION_CALL lazy -> known_edge once.js:14:1 lazy app.js:19:30 CONSTRUCTOR_CALL AlphaPlugin -> implicit_constructor - app.js:22:3 FUNCTION_CALL firstAlpha -> known_edge once.js:5:10 +app.js:22:3 FUNCTION_CALL firstAlpha -> multi_inferred app.js:16:25 app.js:22:3 METHOD_CALL firstAlpha().run -> known_edge alpha.js:3:3 run app.js:23:3 FUNCTION_CALL makeBeta -> known_edge once.js:15:10 +app.js:23:3 FUNCTION_CALL makeBeta -> multi_inferred app.js:17:23 app.js:23:3 METHOD_CALL makeBeta().run -> known_edge beta.js:3:3 run app.js:24:3 FUNCTION_CALL firstBeta -> known_edge once.js:5:10 +app.js:24:3 FUNCTION_CALL firstBeta -> multi_inferred app.js:18:24 app.js:24:3 METHOD_CALL firstBeta().run -> known_edge beta.js:3:3 run app.js:25:3 FUNCTION_CALL makeAlpha -> known_edge once.js:15:10 +app.js:25:3 FUNCTION_CALL makeAlpha -> multi_inferred app.js:19:24 app.js:25:3 METHOD_CALL makeAlpha().run -> known_edge alpha.js:3:3 run app.js:29:18 FUNCTION_CALL constant -> known_edge once.js:18:1 constant app.js:29:27 CONSTRUCTOR_CALL BetaPlugin -> implicit_constructor - @@ -57,10 +63,14 @@ declared.js:27:16 FUNCTION_CALL pickShared -> callback_registered declared.js declared.js:27:16 FUNCTION_CALL pickShared -> known_edge declared.js:20:1 pickShared declared.js:27:33 CONSTRUCTOR_CALL BetaPlugin -> implicit_constructor - declared.js:30:3 FUNCTION_CALL declAlpha -> known_edge declared.js:9:3 inner +declared.js:30:3 FUNCTION_CALL declAlpha -> multi_inferred declared.js:22:28 declared.js:30:3 METHOD_CALL declAlpha().run -> known_edge alpha.js:3:3 run declared.js:31:3 FUNCTION_CALL declBeta -> known_edge declared.js:9:3 inner +declared.js:31:3 FUNCTION_CALL declBeta -> multi_inferred declared.js:23:27 declared.js:31:3 METHOD_CALL declBeta().run -> known_edge beta.js:3:3 run declared.js:32:3 FUNCTION_CALL declOfGetter -> known_edge declared.js:9:3 inner +declared.js:32:3 FUNCTION_CALL declOfGetter -> multi_inferred declared.js:24:26 +declared.js:32:3 FUNCTION_CALL declOfGetter -> multi_inferred memoize.js:5:10 declared.js:32:3 METHOD_CALL declOfGetter().run -> known_edge alpha.js:3:3 run declared.js:33:3 FUNCTION_CALL tappedDecl -> known_edge declared.js:15:3 inner declared.js:33:3 METHOD_CALL tappedDecl().markOnly -> known_edge declared.js:13:14 markOnly @@ -202,24 +212,33 @@ shapes.js:45:27 FUNCTION_CALL memoize -> callback_registered shapes.js:45:35 shapes.js:45:27 FUNCTION_CALL memoize -> known_edge memoize.js:2:17 shapes.js:45:41 CONSTRUCTOR_CALL BetaPlugin -> implicit_constructor - shapes.js:48:3 FUNCTION_CALL getFb -> known_edge shapes.js:15:10 +shapes.js:48:3 FUNCTION_CALL getFb -> multi_inferred shapes.js:36:28 shapes.js:48:3 METHOD_CALL getFb().run -> known_edge alpha.js:3:3 run shapes.js:49:3 FUNCTION_CALL getFb -> known_edge shapes.js:15:10 +shapes.js:49:3 FUNCTION_CALL getFb -> multi_inferred shapes.js:36:28 shapes.js:49:3 METHOD_CALL getFb().fallbackOnly -> known_edge shapes.js:8:18 fallbackOnly shapes.js:50:3 FUNCTION_CALL getOr -> known_edge shapes.js:22:10 +shapes.js:50:3 FUNCTION_CALL getOr -> multi_inferred shapes.js:38:22 shapes.js:50:3 METHOD_CALL getOr().run -> ambient_terminal - shapes.js:50:3 METHOD_CALL getOr().run -> multi_inferred beta.js:3:3 run shapes.js:51:3 FUNCTION_CALL getOr -> known_edge shapes.js:22:10 +shapes.js:51:3 FUNCTION_CALL getOr -> multi_inferred shapes.js:38:22 shapes.js:51:3 METHOD_CALL getOr().mockOnly -> ambient_terminal - shapes.js:51:3 METHOD_CALL getOr().mockOnly -> multi_inferred shapes.js:9:14 mockOnly shapes.js:52:3 FUNCTION_CALL getD -> known_edge shapes.js:29:10 +shapes.js:52:3 FUNCTION_CALL getD -> multi_inferred shapes.js:39:26 shapes.js:52:3 METHOD_CALL getD().run -> known_edge alpha.js:3:3 run shapes.js:53:3 FUNCTION_CALL getD -> known_edge shapes.js:29:10 +shapes.js:53:3 FUNCTION_CALL getD -> multi_inferred shapes.js:39:26 shapes.js:53:3 METHOD_CALL getD().defaultsOnly -> known_edge shapes.js:25:20 defaultsOnly shapes.js:54:3 FUNCTION_CALL getDefault -> known_edge shapes.js:33:44 shapes.js:54:3 METHOD_CALL getDefault().defaultOnly -> known_edge shapes.js:10:19 defaultOnly shapes.js:55:3 FUNCTION_CALL getSpread -> known_edge shapes.js:33:44 shapes.js:55:3 METHOD_CALL getSpread().run -> ambiguous_unknown - shapes.js:56:3 FUNCTION_CALL warm -> known_edge shapes.js:34:32 +shapes.js:56:3 FUNCTION_CALL warm -> multi_inferred shapes.js:43:20 shapes.js:56:3 METHOD_CALL warm().run -> known_edge alpha.js:3:3 run shapes.js:57:3 FUNCTION_CALL getNested -> known_edge memoize.js:5:10 +shapes.js:57:3 FUNCTION_CALL getNested -> multi_inferred memoize.js:5:10 +shapes.js:57:3 FUNCTION_CALL getNested -> multi_inferred shapes.js:45:35 shapes.js:57:3 METHOD_CALL getNested().run -> known_edge beta.js:3:3 run diff --git a/graph/test/javascript/expected/65-wrapper-param-reassigned.edges b/graph/test/javascript/expected/65-wrapper-param-reassigned.edges index d56c695d..8373c4e3 100644 --- a/graph/test/javascript/expected/65-wrapper-param-reassigned.edges +++ b/graph/test/javascript/expected/65-wrapper-param-reassigned.edges @@ -2,51 +2,66 @@ app.js:10:23 CONSTRUCTOR_CALL A -> implicit_constructor - app.js:10:3 FUNCTION_CALL lazyOrDefault -> callback_registered app.js:10:17 app.js:10:3 FUNCTION_CALL lazyOrDefault -> known_edge wrap.js:17:1 lazyOrDefault app.js:10:3 FUNCTION_CALL lazyOrDefault(() => new A()) -> known_edge wrap.js:19:10 +app.js:10:3 FUNCTION_CALL lazyOrDefault(() => new A()) -> multi_inferred app.js:10:17 +app.js:10:3 FUNCTION_CALL lazyOrDefault(() => new A()) -> multi_inferred wrap.js:18:15 app.js:10:3 METHOD_CALL lazyOrDefault(() => new A())().aOnly -> known_edge wrap.js:2:11 aOnly app.js:11:3 FUNCTION_CALL lazyNullish -> known_edge wrap.js:22:1 lazyNullish app.js:11:3 FUNCTION_CALL lazyNullish() -> known_edge wrap.js:24:10 +app.js:11:3 FUNCTION_CALL lazyNullish() -> multi_inferred wrap.js:23:10 app.js:11:3 METHOD_CALL lazyNullish()().dfltOnly -> known_edge wrap.js:4:14 dfltOnly app.js:15:15 CONSTRUCTOR_CALL A -> implicit_constructor - app.js:15:3 FUNCTION_CALL plain -> callback_registered app.js:15:9 app.js:15:3 FUNCTION_CALL plain -> known_edge wrap.js:27:1 plain app.js:15:3 FUNCTION_CALL plain(() => new A()) -> known_edge wrap.js:28:10 +app.js:15:3 FUNCTION_CALL plain(() => new A()) -> multi_inferred app.js:15:9 app.js:15:3 METHOD_CALL plain(() => new A())().aOnly -> known_edge wrap.js:2:11 aOnly app.js:16:15 CONSTRUCTOR_CALL A -> implicit_constructor - app.js:16:3 FUNCTION_CALL plain -> callback_registered app.js:16:9 app.js:16:3 FUNCTION_CALL plain -> known_edge wrap.js:27:1 plain app.js:16:3 FUNCTION_CALL plain(() => new A()) -> known_edge wrap.js:28:10 +app.js:16:3 FUNCTION_CALL plain(() => new A()) -> multi_inferred app.js:16:9 app.js:16:3 METHOD_CALL plain(() => new A())().replacedOnly -> ambiguous_unknown - app.js:17:22 CONSTRUCTOR_CALL A -> implicit_constructor - app.js:17:3 FUNCTION_CALL otherWritten -> callback_registered app.js:17:16 app.js:17:3 FUNCTION_CALL otherWritten -> known_edge wrap.js:31:1 otherWritten app.js:17:3 FUNCTION_CALL otherWritten(() => new A(), null) -> known_edge wrap.js:33:10 +app.js:17:3 FUNCTION_CALL otherWritten(() => new A(), null) -> multi_inferred app.js:17:16 app.js:17:3 METHOD_CALL otherWritten(() => new A(), null)().aOnly -> known_edge wrap.js:2:11 aOnly app.js:18:22 CONSTRUCTOR_CALL A -> implicit_constructor - app.js:18:3 FUNCTION_CALL otherWritten -> callback_registered app.js:18:16 app.js:18:3 FUNCTION_CALL otherWritten -> known_edge wrap.js:31:1 otherWritten app.js:18:3 FUNCTION_CALL otherWritten(() => new A(), null) -> known_edge wrap.js:33:10 +app.js:18:3 FUNCTION_CALL otherWritten(() => new A(), null) -> multi_inferred app.js:18:16 app.js:18:3 METHOD_CALL otherWritten(() => new A(), null)().replacedOnly -> ambiguous_unknown - app.js:4:14 FUNCTION_CALL swapped -> callback_registered app.js:4:22 app.js:4:14 FUNCTION_CALL swapped -> known_edge wrap.js:7:1 swapped app.js:4:28 CONSTRUCTOR_CALL A -> implicit_constructor - app.js:5:3 FUNCTION_CALL sw -> known_edge wrap.js:9:10 +app.js:5:3 FUNCTION_CALL sw -> multi_inferred wrap.js:8:8 app.js:5:3 METHOD_CALL sw().replacedOnly -> known_edge wrap.js:3:18 replacedOnly app.js:6:3 FUNCTION_CALL sw -> known_edge wrap.js:9:10 +app.js:6:3 FUNCTION_CALL sw -> multi_inferred wrap.js:8:8 app.js:6:3 METHOD_CALL sw().aOnly -> ambiguous_unknown - app.js:7:3 FUNCTION_CALL lazyGuard -> known_edge wrap.js:12:1 lazyGuard app.js:7:3 FUNCTION_CALL lazyGuard(undefined) -> known_edge wrap.js:14:10 +app.js:7:3 FUNCTION_CALL lazyGuard(undefined) -> multi_inferred wrap.js:13:38 app.js:7:3 METHOD_CALL lazyGuard(undefined)().replacedOnly -> known_edge wrap.js:3:18 replacedOnly app.js:8:19 CONSTRUCTOR_CALL A -> implicit_constructor - app.js:8:3 FUNCTION_CALL lazyGuard -> callback_registered app.js:8:13 app.js:8:3 FUNCTION_CALL lazyGuard -> known_edge wrap.js:12:1 lazyGuard app.js:8:3 FUNCTION_CALL lazyGuard(() => new A()) -> known_edge wrap.js:14:10 +app.js:8:3 FUNCTION_CALL lazyGuard(() => new A()) -> multi_inferred app.js:8:13 +app.js:8:3 FUNCTION_CALL lazyGuard(() => new A()) -> multi_inferred wrap.js:13:38 app.js:8:3 METHOD_CALL lazyGuard(() => new A())().aOnly -> known_edge wrap.js:2:11 aOnly app.js:9:3 FUNCTION_CALL lazyOrDefault -> known_edge wrap.js:17:1 lazyOrDefault app.js:9:3 FUNCTION_CALL lazyOrDefault() -> known_edge wrap.js:19:10 +app.js:9:3 FUNCTION_CALL lazyOrDefault() -> multi_inferred wrap.js:18:15 app.js:9:3 METHOD_CALL lazyOrDefault()().dfltOnly -> known_edge wrap.js:4:14 dfltOnly many.js:11:3 FUNCTION_CALL s21 -> known_edge wrap.js:9:10 +many.js:11:3 FUNCTION_CALL s21 -> multi_inferred wrap.js:8:8 many.js:11:3 METHOD_CALL s21().replacedOnly -> known_edge wrap.js:3:18 replacedOnly many.js:12:3 FUNCTION_CALL s21 -> known_edge wrap.js:9:10 +many.js:12:3 FUNCTION_CALL s21 -> multi_inferred wrap.js:8:8 many.js:12:3 METHOD_CALL s21().aOnly -> ambiguous_unknown - many.js:3:13 FUNCTION_CALL swapped -> callback_registered many.js:3:21 many.js:3:13 FUNCTION_CALL swapped -> known_edge wrap.js:7:1 swapped diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/case.json b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/case.json new file mode 100644 index 00000000..dcb66027 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/case.json @@ -0,0 +1,16 @@ +{"lang": "javascript", "src": "src", + "checks": [ + {"why": "calling what `promisable(op3Impl, 2)` returned runs op3Impl: the test that calls the export reaches the function the module wrapped", + "run": ["impact", "op3Impl", "--tests"], + "want": ["op3.test.js"], + "avoid": ["tests: 0 of", "op4.test.js"]}, + {"why": "the same through a member of a re-exporting object: lib.op4(...) runs op4Impl, and no other wrapped function", + "run": ["impact", "op4Impl", "--tests"], + "want": ["op4.test.js"], + "avoid": ["op3.test.js"]}, + {"why": "the wrapper's own call of its parameter still holds every wrapped function only per site: op3's test is not a test of op5Impl", + "run": ["impact", "op5Impl", "--tests"], + "avoid": ["op3.test.js", "op4.test.js"]}, + {"why": "CONTROL: a wrapper whose closure never calls its parameter runs nothing it was handed, so calling the labelled export does not reach secret", + "run": ["impact", "secret", "--tests"], + "avoid": ["named.test.js"]}]} diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/index.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/index.js new file mode 100644 index 00000000..0ca257ee --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/index.js @@ -0,0 +1,3 @@ +import op3 from './op3.js'; +import op4 from './op4.js'; +export default { op3, op4 }; diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/label.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/label.js new file mode 100644 index 00000000..f1fd17f2 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/label.js @@ -0,0 +1,3 @@ +export default function label(fn) { + return function labelled() { return fn.name; }; +} diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/named.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/named.js new file mode 100644 index 00000000..2fda0a4c --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/named.js @@ -0,0 +1,3 @@ +import label from './label.js'; +function secret() { return 42; } +export default label(secret); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op0.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op0.js new file mode 100644 index 00000000..512a36e3 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op0.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op0Impl(coll, cb) { return cb(null, coll.length + 0); } +export default promisable(op0Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op1.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op1.js new file mode 100644 index 00000000..64b59b8b --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op1.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op1Impl(coll, cb) { return cb(null, coll.length + 1); } +export default promisable(op1Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op10.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op10.js new file mode 100644 index 00000000..942919bf --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op10.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op10Impl(coll, cb) { return cb(null, coll.length + 10); } +export default promisable(op10Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op11.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op11.js new file mode 100644 index 00000000..fd06b995 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op11.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op11Impl(coll, cb) { return cb(null, coll.length + 11); } +export default promisable(op11Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op12.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op12.js new file mode 100644 index 00000000..0e1cc8ec --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op12.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op12Impl(coll, cb) { return cb(null, coll.length + 12); } +export default promisable(op12Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op13.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op13.js new file mode 100644 index 00000000..7333efb6 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op13.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op13Impl(coll, cb) { return cb(null, coll.length + 13); } +export default promisable(op13Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op14.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op14.js new file mode 100644 index 00000000..2151714c --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op14.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op14Impl(coll, cb) { return cb(null, coll.length + 14); } +export default promisable(op14Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op15.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op15.js new file mode 100644 index 00000000..39b600b4 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op15.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op15Impl(coll, cb) { return cb(null, coll.length + 15); } +export default promisable(op15Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op16.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op16.js new file mode 100644 index 00000000..4b219615 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op16.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op16Impl(coll, cb) { return cb(null, coll.length + 16); } +export default promisable(op16Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op17.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op17.js new file mode 100644 index 00000000..3f40d043 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op17.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op17Impl(coll, cb) { return cb(null, coll.length + 17); } +export default promisable(op17Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op18.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op18.js new file mode 100644 index 00000000..2844d30b --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op18.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op18Impl(coll, cb) { return cb(null, coll.length + 18); } +export default promisable(op18Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op19.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op19.js new file mode 100644 index 00000000..5ae0a6cb --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op19.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op19Impl(coll, cb) { return cb(null, coll.length + 19); } +export default promisable(op19Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op2.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op2.js new file mode 100644 index 00000000..6332264f --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op2.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op2Impl(coll, cb) { return cb(null, coll.length + 2); } +export default promisable(op2Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op20.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op20.js new file mode 100644 index 00000000..f69644bc --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op20.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op20Impl(coll, cb) { return cb(null, coll.length + 20); } +export default promisable(op20Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op21.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op21.js new file mode 100644 index 00000000..bfed47cf --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op21.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op21Impl(coll, cb) { return cb(null, coll.length + 21); } +export default promisable(op21Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op22.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op22.js new file mode 100644 index 00000000..b7b24386 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op22.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op22Impl(coll, cb) { return cb(null, coll.length + 22); } +export default promisable(op22Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op23.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op23.js new file mode 100644 index 00000000..bf228d69 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op23.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op23Impl(coll, cb) { return cb(null, coll.length + 23); } +export default promisable(op23Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op3.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op3.js new file mode 100644 index 00000000..19c2ac86 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op3.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op3Impl(coll, cb) { return cb(null, coll.length + 3); } +export default promisable(op3Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op4.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op4.js new file mode 100644 index 00000000..f38f30c4 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op4.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op4Impl(coll, cb) { return cb(null, coll.length + 4); } +export default promisable(op4Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op5.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op5.js new file mode 100644 index 00000000..1898ad42 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op5.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op5Impl(coll, cb) { return cb(null, coll.length + 5); } +export default promisable(op5Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op6.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op6.js new file mode 100644 index 00000000..820234a4 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op6.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op6Impl(coll, cb) { return cb(null, coll.length + 6); } +export default promisable(op6Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op7.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op7.js new file mode 100644 index 00000000..53e4e1a6 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op7.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op7Impl(coll, cb) { return cb(null, coll.length + 7); } +export default promisable(op7Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op8.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op8.js new file mode 100644 index 00000000..873712ec --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op8.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op8Impl(coll, cb) { return cb(null, coll.length + 8); } +export default promisable(op8Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op9.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op9.js new file mode 100644 index 00000000..f7dfa907 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/op9.js @@ -0,0 +1,3 @@ +import promisable from './promisable.js'; +function op9Impl(coll, cb) { return cb(null, coll.length + 9); } +export default promisable(op9Impl, 2); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/promisable.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/promisable.js new file mode 100644 index 00000000..85733214 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/lib/promisable.js @@ -0,0 +1,12 @@ +export default function promisable(fn, arity) { + function run(...args) { + if (typeof args[arity - 1] === 'function') { + return fn.apply(this, args); + } + return new Promise((resolve) => { + args[arity - 1] = (err, value) => resolve(value); + fn.apply(this, args); + }); + } + return run; +} diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/package.json b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/package.json new file mode 100644 index 00000000..5a4a9bf6 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/package.json @@ -0,0 +1 @@ +{"name": "wrapkit", "type": "module"} diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/test/named.test.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/test/named.test.js new file mode 100644 index 00000000..82c77371 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/test/named.test.js @@ -0,0 +1,2 @@ +import named from '../lib/named.js'; +it('named', () => { named(); }); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/test/op3.test.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/test/op3.test.js new file mode 100644 index 00000000..9ef95bfc --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/test/op3.test.js @@ -0,0 +1,2 @@ +import op3 from '../lib/op3.js'; +it('op3', (done) => { op3([1], done); }); diff --git a/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/test/op4.test.js b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/test/op4.test.js new file mode 100644 index 00000000..c82a1032 --- /dev/null +++ b/tests/cases/javascript/call-through-a-wrapper-runs-its-argument/src/test/op4.test.js @@ -0,0 +1,2 @@ +import lib from '../lib/index.js'; +it('op4', (done) => { lib.op4([1, 2], done); });