test(carriers): pin member calls and deeply nested computed arguments

Two first-round review findings were never addressed, and both reproduce
on the current head:

- records.require('./label'), obj?.import('./x') and obj . require('./y')
  are method calls, but the call pattern's word boundary is satisfied by
  the dot, so a self-contained object method fails every projection for a
  file the script never loads.
- The dynamic-argument pattern allows one level of nested parentheses, so
  require(path.join(__dirname, getName())) produces no dynamic warning
  while require(getName()) does.

Control: a genuine require beside the member calls is still a dependency.
This commit is contained in:
montjeffrey
2026-10-10 18:00:50 -04:00
parent d9fe97171d
commit 7497de02ab
@@ -137,6 +137,13 @@ test('an extensionless require resolves to a .json file', () => withFixture(root
assert.equal(result.status, 'success', JSON.stringify(result.failures));
}));
test('an object method named require is not a module load', () => withFixture(root => {
plantSkillScript(root, "const records = { require: name => name };\nmodule.exports = records.require('./label');\n");
const result = validator().validate(root);
assert.equal(result.status, 'success', JSON.stringify(result.failures));
assert.deepEqual(result.warnings, []);
}));
test('a tagged template named from is not an import', () => withFixture(root => {
plantSkillScript(root, 'const from = parts => parts[0];\nmodule.exports = from`./helper`;\n');
const result = validator().validate(root);
@@ -221,8 +228,16 @@ const SCANNER_CASES = [
"const n = obj?. while(x) / 2, q = \"/\"; require('./after-optional');", ['./after-optional']],
['a tagged template named from', 'const from = p => p[0]; const s = from`./helper`;', []],
['a template import() is still a dependency', 'import(`./templated`);', ['./templated']],
['member calls named require or import',
"r.require('./label'); obj?.import('./x'); obj . require('./y'); obj.\nimport('./z'); require('./real');", ['./real']],
];
test('scanner: computed arguments nested more than one level deep still warn', () => {
const { dynamic } = validator().extractReferences(
"require(path.join(__dirname, getName())); require(a(b(c()))); obj.require(hidden());");
assert.deepEqual(dynamic.sort(), ['a(b(c()))', 'path.join(__dirname, getName())']);
});
for (const [label, source, expected] of SCANNER_CASES) {
test(`scanner: ${label}`, () => {
assert.deepEqual(validator().extractReferences(source).specifiers.sort(), [...expected].sort());