-
Notifications
You must be signed in to change notification settings - Fork 0
D3-CX-F8 — Prevent comment false re-export matches #60
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -73,7 +73,17 @@ function extractModuleSpecifiers(source: string): readonly string[] { | |
| // optional whitespace and/or block comments between `from` and the module | ||
| // string. The separator after `from` is `\s*`, not `\s+`, so a block comment | ||
| // (or the quote itself) may abut `from` directly, e.g. `from/* c */'S'`. | ||
| /\bexport\b(?:\/\*[\s\S]*?\*\/|[^'"])*?\bfrom\s*(?:\/\*[\s\S]*?\*\/\s*)*['"]([^'"]+)['"]/g, | ||
| // | ||
| // The prefix between `export` and `from` may traverse a whole block comment | ||
| // (consumed as one unit), a genuine lone `/` that does not start a comment, | ||
| // or any other character that is neither a quote, a `;`, nor a `/`. It must | ||
| // NOT enter comment *text*: a `//` line comment cannot be traversed, a `/*` | ||
| // is only crossable as a complete unit, and a `;` statement terminator stops | ||
| // the prefix. This keeps a comment-contained `from` — e.g. a doc line | ||
| // `export const x = true; // from'../domain/y.js'` — from fabricating a | ||
| // re-export dependency (D3-CX-F8), while still detecting a real re-export | ||
| // whose `from` carries an adjacent block comment (D3-CR-F6/F7). | ||
| /\bexport\b(?:\/\*[\s\S]*?\*\/|\/(?![*/])|[^'";/])*?\bfrom\s*(?:\/\*[\s\S]*?\*\/\s*)*['"]([^'"]+)['"]/g, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
When host code uses a valid string-named re-export such as Useful? React with 👍 / 👎. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift 🔎 Supported by static analysis🏁 Script executed: #!/bin/bash
set -eu
printf '%s\n' '--- diff ---'
git diff -- tests/cockpit-host/purity.test.ts
printf '%s\n' '--- file outline ---'
ast-grep outline tests/cockpit-host/purity.test.ts
printf '%s\n' '--- relevant source ---'
sed -n '55,105p;425,580p' tests/cockpit-host/purity.test.tsRepository: LogicDuke/agentbridge Length of output: 10175 🏁 Script executed: #!/bin/bash
set -eu
node - <<'JS'
const pattern =
/\bexport\b(?:\/\*[\s\S]*?\*\/|\/(?![*/])|[^'";/])*?\bfrom\s*(?:\/\*[\s\S]*?\*\/\s*)*['"]([^'"]+)['"]/g;
const inputs = {
lineCommentBeforeFrom: "export { x } // note\nfrom '../domain/foo.js';",
blockCommentBeforeFrom: "export { x } /* note */ from '../domain/foo.js';",
commentAfterFrom: "export { x } from /* note */ '../domain/foo.js';",
};
for (const [name, source] of Object.entries(inputs)) {
console.log(name, JSON.stringify([...source.matchAll(pattern)].map((m) => m[1])));
}
JSRepository: LogicDuke/agentbridge Length of output: 266 Preserve re-exports that use a line comment before
🤖 Prompt for AI Agents |
||
| ]; | ||
| const specifiers: string[] = []; | ||
| for (const pattern of patterns) { | ||
|
|
@@ -432,3 +442,124 @@ describe('D3 host import scanner covers `from`-abutting re-export comments (D3-C | |
| expect(extractModuleSpecifiers(`const isEntry = import.meta.url === x;`)).toEqual([]); | ||
| }); | ||
| }); | ||
|
|
||
| describe('D3 host import scanner rejects comment-contained re-export `from` (D3-CX-F8)', () => { | ||
| const forbiddenIn = (source: string): readonly string[] => | ||
| extractModuleSpecifiers(source).filter((s) => /\.\.\/(?:domain|adapters)\//.test(s)); | ||
|
|
||
| // D3-CX-F8: the re-export prefix between `export` and `from` could nibble into | ||
| // comment text and bind a comment-contained `from` as though it were the real | ||
| // re-export clause, fabricating a dependency from a mere doc comment. The | ||
| // prefix now stops at `;`, cannot enter a `//` line comment, and only crosses | ||
| // a `/* ... */` as a whole unit — so none of the following valid sources may | ||
| // surface `../domain/example.js`. Each fixture is syntactically valid ESM | ||
| // (the declaration is terminated by `;` or by ASI before a trailing comment). | ||
| const falsePositiveFixtures: readonly { readonly form: string; readonly source: string }[] = [ | ||
| { | ||
| form: 'line comment, `from` abutting the quote', | ||
| source: `export const safe = true; // docs: from'../domain/example.js'`, | ||
| }, | ||
| { | ||
| form: 'line comment, whitespace before the quote', | ||
| source: `export const safe = true; // docs: from '../domain/example.js'`, | ||
| }, | ||
| { | ||
| form: 'block comment, `from` abutting the quote', | ||
| source: `export const safe = true; /* docs: from'../domain/example.js' */`, | ||
| }, | ||
| { | ||
| form: 'multi-line block comment', | ||
| source: ['export const safe = true;', '/* docs:', " from'../domain/example.js'", '*/'].join( | ||
| '\n', | ||
| ), | ||
| }, | ||
| { | ||
| form: 'ASI (no semicolon), trailing block comment', | ||
| source: `export const x = true /* docs: from'../domain/example.js' */`, | ||
| }, | ||
| { | ||
| form: 'ASI (no semicolon), block comment on the next line', | ||
| source: ['export const x = true', "/* docs: from'../domain/example.js' */"].join('\n'), | ||
| }, | ||
| { | ||
| form: 'ASI (no semicolon), trailing line comment', | ||
| source: ['export const x = true', "// docs: from'../domain/example.js'"].join('\n'), | ||
| }, | ||
| ]; | ||
|
|
||
| for (const { form, source } of falsePositiveFixtures) { | ||
| it(`does not extract a comment-contained module (${form})`, () => { | ||
| expect(extractModuleSpecifiers(source)).not.toContain('../domain/example.js'); | ||
| expect(forbiddenIn(source)).toEqual([]); | ||
| }); | ||
| } | ||
|
|
||
| // Positive controls: a real re-export whose `from` carries an adjacent comment | ||
| // must still be detected exactly once — the narrowing must not regress F6/F7. | ||
| it('still detects a real re-export with a block comment abutting `from` (F7)', () => { | ||
| expect(forbiddenIn(`export { x } from/* note */'../domain/foo.js';`).length).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| it('still detects a real re-export with a whitespace+comment before the module (F6)', () => { | ||
| expect(forbiddenIn(`export { x } from /* note */ '../domain/foo.js';`).length).toBeGreaterThan( | ||
| 0, | ||
| ); | ||
| }); | ||
|
|
||
| it('still detects star, type, and double-quoted re-exports with adjacent comments', () => { | ||
| expect(forbiddenIn(`export * from/* note */'../domain/foo.js';`).length).toBeGreaterThan(0); | ||
| expect( | ||
| forbiddenIn(`export type { T } from/* note */'../domain/foo.js';`).length, | ||
| ).toBeGreaterThan(0); | ||
| expect(forbiddenIn(`export * from/* note */"../adapters/foo.js";`).length).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| it('still consumes a prefix block comment as a unit, even one containing quotes or `from`', () => { | ||
| // A block comment BEFORE the real `from` (holding a quote, or the word | ||
| // `from`) must be crossed whole, and only the genuine specifier surfaces. | ||
| expect(extractModuleSpecifiers(`export /* 'note' */ { x } from '../domain/foo.js';`)).toEqual([ | ||
| '../domain/foo.js', | ||
| ]); | ||
| expect(extractModuleSpecifiers(`export /* from 'x' */ { a } from '../domain/foo.js';`)).toEqual([ | ||
| '../domain/foo.js', | ||
| ]); | ||
| }); | ||
|
|
||
| it('still tolerates multiple and multi-line comments after `from`', () => { | ||
| expect(forbiddenIn(`export { x } from/* a *//* b */'../domain/foo.js';`).length).toBeGreaterThan( | ||
| 0, | ||
| ); | ||
| const multiline = ['export { x } from /* multi', " line */ '../domain/foo.js';"].join('\n'); | ||
| expect(forbiddenIn(multiline).length).toBeGreaterThan(0); | ||
| }); | ||
|
|
||
| it('does not treat an identifier beginning with `from` as the clause keyword', () => { | ||
| expect(extractModuleSpecifiers(`export const fromValues = 1;`)).toEqual([]); | ||
| expect(extractModuleSpecifiers(`export const from_foo = true;`)).toEqual([]); | ||
| expect(extractModuleSpecifiers(`export { y } fromX '../domain/foo.js';`)).toEqual([]); | ||
| expect(extractModuleSpecifiers(`export { y } from1 '../domain/foo.js';`)).toEqual([]); | ||
| }); | ||
|
|
||
| it('extracts across statements exactly once, without a comment bridging them', () => { | ||
| const source = [ | ||
| "export { a } from/* note */'./local.js';", | ||
| "export const doc = true; // from'../domain/example.js'", | ||
| "export { b } from '../cockpit/index.js';", | ||
| ].join('\n'); | ||
| expect(extractModuleSpecifiers(source)).toEqual(['./local.js', '../cockpit/index.js']); | ||
| }); | ||
|
|
||
| it('preserves static, dynamic, allowed, and import.meta behaviour under F8 narrowing', () => { | ||
| expect(forbiddenIn(`import /* note */ '../domain/foo.js';`).length).toBeGreaterThan(0); | ||
| expect(forbiddenIn(`import(/* note */ '../domain/foo.js')`).length).toBeGreaterThan(0); | ||
| expect( | ||
| extractModuleSpecifiers(`import('../domain/foo.js', { with: { type: 'json' } })`), | ||
| ).toContain('../domain/foo.js'); | ||
| expect(extractModuleSpecifiers(`import http from 'node:http';`)).toContain('node:http'); | ||
| expect(extractModuleSpecifiers(`import { a } from "./local.js";`)).toContain('./local.js'); | ||
| expect(extractModuleSpecifiers(`import { r } from '../cockpit/index.js';`)).toContain( | ||
| '../cockpit/index.js', | ||
| ); | ||
| expect(extractModuleSpecifiers(`const isEntry = import.meta.url === x;`)).toEqual([]); | ||
| }); | ||
| }); | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When a valid re-export contains a line comment before its real
from—for example,export { x, // note\n y } from '../domain/foo.js';—the new prefix stops at//, soextractModuleSpecifiersreturns no specifier. This lets a forbidden domain or adapter dependency bypass both import-discipline checks; line comments inside a named export list need to be skipped without allowing comments after a completed export statement to fabricate a match.Useful? React with 👍 / 👎.