Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions plugins/axiomcode/skills/axiomcode/scripts/dl/impact.dl
Original file line number Diff line number Diff line change
Expand Up @@ -919,11 +919,18 @@ parent_up(q, a, b, "protocol") :- reach(q, a, d), d > 0, reach(q, b, d1), d1 = d
// (`imports()` in axiomcode-impact, per language). Only a target that resolves to a file IN THIS REPOSITORY is
// kept: a stdlib or third-party import names no file here and is dropped rather than guessed.
.decl imports_fact(f:symbol, g:symbol) .input imports_fact
// ASKED FOR, NOT PRECOMPUTED (#1033). `import_hop` only ever reads the files that import a module the change
// reaches, so the closure is walked backwards from those files and nowhere else. The whole-project closure this
// replaces was the same for every question and quadratic to build (imports_file joined to itself): on a
// 3.6k-file TypeScript graph it was 1.2M rows and 27 s of a 38 s solve, the flat floor under every `impact`.
.decl import_goal(g:symbol)
import_goal(g) :- up_running(_, mod, _), kind(mod, "module"), decl_file(mod, g).
.decl imports_file(f:symbol, g:symbol)
imports_file(f, g) :- imports_fact(f, g).
imports_file(f, g) :- import_goal(g), imports_fact(f, g).
// and transitively: a test file imports the module that imports the settings object. The chain is what makes
// the rule worth anything — the file that breaks is almost never the file the test names.
imports_file(f, h) :- imports_file(f, g), imports_file(g, h), f != h.
// the rule worth anything — the file that breaks is almost never the file the test names. Linear in the chain:
// any path of imports from f to g with f != g yields the pair, the same set the self-join derived.
imports_file(f, g) :- imports_fact(f, h), imports_file(h, g), f != g.
.output fw_edge

// ── tests: a test's own body reaches the change, or a fixture its framework runs before it does ───────────────
Expand Down
10 changes: 10 additions & 0 deletions tests/cases/typescript/runs-at-import-through-a-chain/case.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{"lang": "typescript", "src": "src",
"checks": [
{"why": "a module-level call breaks every file that imports its module, however many imports away: page.spec reaches the table only through page -> escape -> render -> tags, and escape and render import each other",
"run": ["impact", "makeMap", "--tests"],
"want": ["page.spec.ts", "at import"],
"avoid": ["sum.spec.ts"]},
{"why": "control: a chain of imports that never reaches a module doing work at import propagates nothing",
"run": ["impact", "double", "--tests"],
"want": ["sum.spec.ts"],
"avoid": ["page.spec.ts"]}]}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { describe, it, expect } from 'vitest'
import { page } from '../page'

describe('page', () => {
it('wraps', () => {
expect(page('x')).toBe('<b>x')
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { describe, it, expect } from 'vitest'
import { sum } from '../sum'

describe('sum', () => {
it('adds', () => {
expect(sum(1, 2)).toBe(4)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { open } from './render'

export function escape(s: string): string {
return s.replace(/</g, '&lt;')
}

export function wrap(s: string): string {
return open('b') + s
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// A helper CALLED AT MODULE LEVEL to build a lookup table.
export function makeMap(list: string): Record<string, boolean> {
const out: Record<string, boolean> = {}
for (const k of list.split(',')) out[k] = true
return out
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { wrap } from './escape'

// two imports away from the table module, and calls nothing in it
export function page(s: string): string {
return wrap(s)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// imports nothing that reaches the table module
export function double(n: number): number {
return n * 2
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { isVoidTag } from './tags'
import { escape } from './escape'

// imports the table module, and is imported by `escape`, which it imports: a cycle
export function open(tag: string): string {
return '<' + escape(tag) + '>'
}

export const voids = Object.keys(isVoidTag)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { double } from './plain'

export function sum(a: number, b: number): number {
return double(a) + b
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { makeMap } from './maps'

// the module body runs this while being imported
export const isVoidTag = makeMap('br,hr,img')
Loading