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
30 changes: 28 additions & 2 deletions .claude/skills/records/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ Signed-off-by: Name <email>
side, or once, with the captions, when the two are equal; a form with
one version across the block under its version's label. Trees, code,
text, matrices and mappings are drawn on aligned rows, a line common to
both versions facing itself and the changed lines shaded; typing rules
both versions facing itself and the changed lines shaded; reanalyze
reports are drawn as rows of findings, aligned the same way; typing rules
and rewrites are drawn as two figures side by side, the changed parts
shaded: premises, conclusions, rule names and formulas, and each term of
a rewrite against the term at its position.
Expand All @@ -95,6 +96,7 @@ The table lists the common kinds.
| `Diagnostic` | an error or warning |
| `Format` | the formatter's output |
| `Representation` | how a value is represented in JavaScript |
| `Reanalyze` | reanalyze's findings for a program |

## Forms

Expand All @@ -111,7 +113,16 @@ The table lists the common kinds.
| `rule` | inference rules and formulas | inference rule |
| `matrix` | a clause matrix | matrix |
| `representation` | how ReScript values map to JavaScript | mapping |

| `reanalyze` | the report exactly as reanalyze prints it | findings |

- A form holds a representative selection of the output, enough to see
what changes and what stays; an example shows only the cases that
matter. A line `…`, alone or followed by text such as
`… 4 more like this`, marks the lines left out and is drawn muted.
- The forms are the output for the example as shown, so the locations and
names in them point into the example. Forms taken from a larger file,
such as a test fixture, name that file in a caption, for example
`Before (reanalyze): tests/…/Division.res`.
- A tree is indented lines, two spaces a level, as the compiler prints it:
`bsc -bs-no-builtin-ppx -only-parse -dparsetree file.res` prints the
parse tree, `bsc -only-parse -dparsetree` the expanded one, and
Expand All @@ -131,6 +142,21 @@ The table lists the common kinds.
cell a pattern; `label: text` lines are notes under it.
- A `representation` is `ReScript ↦ JavaScript` lines; `label: text` lines
are notes among them.
- A `reanalyze` report is findings separated by blank lines, each an
analysis line, a location line and the message. A report shows, for
example, one finding that disappears, one that stays and the closing
line with the totals, and a paragraph `…` stands for the findings left
out. Each finding is a row: the analysis tagged, the location, the
message; an analysis that every finding names tags the form once.
Before and After align findings by their text: a run of findings in
both reports is one row that opens to show them, a finding one report
lacks faces a gap, and a note that changes, such as the closing count,
faces its other version with the differing words marked. In a built
project such as
`tests/analysis_tests/tests-reanalyze/deadcode` (`yarn build` there),
`dune exec rescript-tools -- reanalyze -exception -ci -suppress src -unsuppress src/<dir>`
prints the exception analysis's report for the files under `src/<dir>`;
`-dce` and `-termination` select the other analyses.
- A form of another name is drawn as text.

## When to add a block
Expand Down
1 change: 1 addition & 0 deletions changes/semantic-diff-reanalyze.internal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- Semantic Diff pages draw reanalyze reports as findings, Before and After aligned by finding.
150 changes: 150 additions & 0 deletions scripts/commitRecords.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -670,3 +670,153 @@ ${SIGNED}
has(html, '<span class="stamp">/1002</span>');
has(html, '<span class="p">#raw_expr</span>');
});

const DIVIDE = `Exception Analysis
Division.res:1:5-10
divide might throw Division_by_zero (Division.res:1:27) and is not annotated with @throws(Division_by_zero)`;
const DIVIDE_BY = `Exception Analysis
Division.res:2:5-12
divideBy might throw Division_by_zero (Division.res:2:37) and is not annotated with @throws(Division_by_zero)`;

const DEAD = `Warning Dead Value
DeadTest.res:3:1-10
deadValue is never used`;

test("a reanalyze report draws its findings aligned by identity, one analysis tagging the form", async () => {
const html = await renderOne(`Skip literal divisors

Reanalyze modified (division):
Before (reanalyze):
${indented(`\n${DIVIDE}\n\n${DIVIDE_BY}`, " ")}
After (reanalyze):
${indented(`\n${DIVIDE_BY}`, " ")}

${SIGNED}
`);
has(
html,
'<h5 class="form"><span class="lang">reanalyze</span> <span class="tag">Exception Analysis</span></h5>',
);
has(html, '<div class="report rows-report">');
has(
html,
'<div class="finding del" style="--r:1;--c:1"><div class="where"><code class="loc">Division.res:1:5-10</code></div><div class="msg">divide might throw Division_by_zero (<span class="ref">Division.res:1:27</span>) and is not annotated with <span class="a">@throws(Division_by_zero)</span></div></div>',
);
has(html, '<div class="finding gap" style="--r:1;--c:2"></div>');
has(
html,
'<details class="same" style="--r:2"><summary>1 unchanged finding</summary><div class="report rows-report"><div class="finding" style="--r:1;--c:1"><div class="where"><code class="loc">Division.res:2:5-12</code>',
);
has(html, '<div class="finding dup" style="--r:1;--c:2">');
assert.equal(html.split('<div class="finding del"').length - 1, 1);
lacks(html, '<div class="finding add"');
lacks(html, '<div class="where"><span class="tag">');
});

test("an added finding is shaded added, and findings of several analyses keep their tags", async () => {
const html = await renderOne(`Report dead values

Reanalyze modified (dead code):
Before (reanalyze):
${indented(`${DIVIDE}\n\nAnalysis reported 1 issues`, " ")}
After (reanalyze):
${indented(`${DEAD}\n\n${DIVIDE}\n\nAnalysis reported 2 issues (Warning Dead Value:1)`, " ")}

Reanalyze added (termination):
After (reanalyze):
Error Termination
TestCyberTruck.res:29:28-39
Possible infinite loop when calling alwaysLoop
CallStack:
1 alwaysLoop (TestCyberTruck.res:29)

${SIGNED}
`);
has(html, '<h5 class="form"><span class="lang">reanalyze</span></h5>');
has(
html,
'<div class="finding add" style="--r:1;--c:2"><div class="where"><span class="tag">Warning Dead Value</span><code class="loc">DeadTest.res:3:1-10</code></div><div class="msg">deadValue is never used</div></div>',
);
has(html, '<div class="finding gap" style="--r:1;--c:1"></div>');
has(
html,
'<summary>1 unchanged finding</summary><div class="report rows-report"><div class="finding" style="--r:1;--c:1"><div class="where"><span class="tag">Exception Analysis</span>',
);
has(
html,
'<div class="finding note mod" style="--r:3;--c:1"><div class="msg">Analysis reported <span class="del">1</span> issues</div></div>',
);
has(
html,
'<div class="finding note mod" style="--r:3;--c:2"><div class="msg">Analysis reported <span class="add">2</span> issues <span class="add">(</span>',
);
has(
html,
'<h5 class="form"><span class="lang">reanalyze</span> <span class="tag">Error Termination</span></h5><figure class="one"><figcaption class="vlabel">After</figcaption><div class="report"><div class="finding"><div class="where"><code class="loc">TestCyberTruck.res:29:28-39</code></div><div class="msg">Possible infinite loop when calling alwaysLoop\nCallStack:\n 1 alwaysLoop (TestCyberTruck.res:29)</div></div></div></figure>',
);
});

test("a run of unchanged findings is one row, and a changed count is one row", async () => {
const html = await renderOne(`Skip literal divisors

Reanalyze modified (division):
Before (reanalyze):
${indented(`\n${DIVIDE}\n\n${DIVIDE_BY}\n\n${DIVIDE_BY.replaceAll("divideBy", "divideTo")}\n\nAnalysis reported 3 issues (Exception Analysis:3)`, " ")}
After (reanalyze):
${indented(`\n${DIVIDE_BY}\n\n${DIVIDE_BY.replaceAll("divideBy", "divideTo")}\n\nAnalysis reported 2 issues (Exception Analysis:2)`, " ")}

${SIGNED}
`);
has(
html,
'<details class="same" style="--r:2"><summary>2 unchanged findings</summary>',
);
has(html, '<div class="finding" style="--r:2;--c:1"><div class="where">');
has(html, '<div class="finding dup" style="--r:2;--c:2"><div class="where">');
has(html, "divideTo might throw");
assert.equal(html.split('<details class="same"').length - 1, 1);
has(
html,
'<div class="finding note mod" style="--r:3;--c:1"><div class="msg">Analysis reported <span class="del">3</span> issues (Exception Analysis:<span class="del">3</span>)</div></div>',
);
has(
html,
'<div class="finding note mod" style="--r:3;--c:2"><div class="msg">Analysis reported <span class="add">2</span> issues (Exception Analysis:<span class="add">2</span>)</div></div>',
);
lacks(html, '<div class="finding note del"');
lacks(html, '<div class="finding note add"');
has(
html,
".finding { background:var(--surface); border:1px solid var(--rule); border-radius:6px;",
);
});

test("an elision in a report is a muted row, and in code a muted line", async () => {
const html = await renderOne(`Skip literal divisors

Reanalyze modified (division):
Before (reanalyze):
${indented(`${DIVIDE}\n\n… 4 more like this\n\n${DIVIDE_BY}`, " ")}
After (reanalyze):
${indented(`${DIVIDE_BY}`, " ")}

JS modified (division):
Before (js):
let a = 1;
…
After (js):
let a = 2;
… 3 more lines

${SIGNED}
`);
has(
html,
'<div class="finding elide" style="--r:2;--c:1"><div class="msg">… 4 more like this</div></div>',
);
has(html, '<div class="finding gap" style="--r:2;--c:2"></div>');
lacks(html, "finding elide del");
has(html, '<span class="ln elide" style="--r:3;--c:1;--i:0">…</span>');
has(html, '<span class="ln elide" style="--r:3;--c:2;--i:0">… ');
has(html, '<span class="ln del" style="--r:2;--c:1;--i:0">');
});
Loading
Loading