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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
- Procedures now record how they handle errors — whether they have a handler, silently suppress errors, or have no protection at all — so unguarded code paths can be found without reading every module. (#259)
- An Access project that ships a generated structure export of its backend now gets real tables in the graph, with their columns, and linked tables point at the external database file they actually live in — so a table found from a query is the same table that carries its fields. (#257)
- Work a procedure does only when something goes wrong is now marked as such, and each error handler records whether it records the message, shows it to the user, re-raises it, or does more than one of those — so a procedure's failure path can be told apart from its normal one. (#260)
- Each error handler in Access code is now its own symbol you can search for and jump to, linked to the procedure that routes errors to it, so a handler can be found and followed directly instead of only asking whether a procedure has one. (#263)

### Changed

Expand Down
11 changes: 9 additions & 2 deletions __tests__/extraction-vba-error-handler-region.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ describe('VBA error-handler region — inErrorHandler (issue #260)', () => {
expect(flagged(opens[1])).toBe(true);
});

it('adds no node, no edge and no unresolved reference', () => {
it('adds no node, no edge and no unresolved reference of its own', () => {
const body = [
'Public Sub Guardar()',
' On Error GoTo errores',
Expand All @@ -226,8 +226,15 @@ describe('VBA error-handler region — inErrorHandler (issue #260)', () => {
),
);

// Issue #263 (task E6) later added the `handles-error` edge onto the
// label node — the one sanctioned addition in this wave, and the only
// row either side does not share. Set it aside and the invariant this
// task is judged by still holds: #260 stamps a FIELD, it creates nothing.
const rows = (r: typeof withHandler) =>
r.edges.filter((e) => e.kind !== 'handles-error');

expect(withHandler.nodes).toHaveLength(withoutRegion.nodes.length);
expect(withHandler.edges).toHaveLength(withoutRegion.edges.length);
expect(rows(withHandler)).toHaveLength(rows(withoutRegion).length);
expect(withHandler.unresolvedReferences).toHaveLength(
withoutRegion.unresolvedReferences.length,
);
Expand Down
61 changes: 45 additions & 16 deletions __tests__/extraction-vba-error-policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { describe, expect, it } from 'vitest';
import { VbaExtractor } from '../src/extraction/vba-extractor';
import { VBA_RULE_TABLES } from '../src/extraction/vba-extractor';
import { RULES } from '../src/extraction/vba/errors';
import { Node } from '../src/types';
import { Edge, Node } from '../src/types';

interface ErrorPolicy {
protection: 'handler' | 'resume-next' | 'none';
Expand Down Expand Up @@ -67,14 +67,17 @@ function policy(nodes: Node[], name: string): ErrorPolicy {
}

describe('Issue #259: the rule table', () => {
it('registers `errors` in VBA_RULE_TABLES with exactly the four tabulated rules', () => {
// The issue tabulates these four ids and no others; they are also the
// handles `codegraph stats vba-rules` reports.
it('registers `errors` in VBA_RULE_TABLES with the four tabulated rules', () => {
// The issue tabulates these four ids; they are also the handles
// `codegraph stats vba-rules` reports. `goto-jump` was appended by issue
// #263, which needs the plain-`GoTo` jumps this table had no reason to
// look at while it emitted nothing.
expect(VBA_RULE_TABLES.errors?.map((r) => r.id)).toEqual([
'on-error-label',
'on-error-resume-next',
'on-error-reset',
'line-label',
'goto-jump',
]);
expect(VBA_RULE_TABLES.errors).toBe(RULES);
});
Expand Down Expand Up @@ -497,11 +500,34 @@ describe('Issue #259: regression guards', () => {
});
});

/**
* Issue #263 (task E6) later added a `label` node and a `handles-error` edge
* on top of this classifier — with maintainer sign-off, and against the
* budget §4.3 of the plan sets out. It is the ONLY thing allowed to add rows
* here, so the guard below still holds once its rows are set aside: the
* error-POLICY classifier itself must remain a pure annotator.
*/
function withoutIssue263Rows(result: {
nodes: Node[];
edges: Edge[];
}): { nodes: Node[]; edges: Edge[] } {
const labelIds = new Set(
result.nodes.filter((n) => n.kind === 'label').map((n) => n.id),
);
return {
nodes: result.nodes.filter((n) => n.kind !== 'label'),
edges: result.edges.filter(
(e) => e.kind !== 'handles-error' && !labelIds.has(e.target),
),
};
}

describe('Issue #259: zero new node kinds, zero new edge kinds', () => {
it('emits no node and no edge for the handler, the label or the policy', () => {
// The merge-blocking constraint of the whole error-handling wave. The
// handler-bearing module must produce exactly the nodes and edges the
// module WITHOUT any `On Error` produces, plus nothing.
it('emits no node and no edge for the handler or the policy', () => {
// The merge-blocking constraint of the whole error-handling wave. Setting
// #263's label rows aside, the handler-bearing module must produce exactly
// the nodes and edges the module WITHOUT any `On Error` produces, plus
// nothing.
const withHandler = extract([
'Public Sub Guardar()',
' On Error GoTo errores',
Expand All @@ -520,20 +546,23 @@ describe('Issue #259: zero new node kinds, zero new edge kinds', () => {
'End Sub',
]);

expect(withHandler.nodes.map((n) => n.kind).sort()).toEqual(
withoutHandler.nodes.map((n) => n.kind).sort(),
const withHandlerRows = withoutIssue263Rows(withHandler);
const withoutHandlerRows = withoutIssue263Rows(withoutHandler);

expect(withHandlerRows.nodes.map((n) => n.kind).sort()).toEqual(
withoutHandlerRows.nodes.map((n) => n.kind).sort(),
);
expect(withHandler.edges.map((e) => e.kind).sort()).toEqual(
withoutHandler.edges.map((e) => e.kind).sort(),
expect(withHandlerRows.edges.map((e) => e.kind).sort()).toEqual(
withoutHandlerRows.edges.map((e) => e.kind).sort(),
);
expect(withHandler.nodes.length).toBe(withoutHandler.nodes.length);
expect(withHandler.edges.length).toBe(withoutHandler.edges.length);
expect(withHandlerRows.nodes.length).toBe(withoutHandlerRows.nodes.length);
expect(withHandlerRows.edges.length).toBe(withoutHandlerRows.edges.length);
expect(withHandler.unresolvedReferences.length).toBe(
withoutHandler.unresolvedReferences.length,
);

// No node is named after the label, in any kind.
expect(withHandler.nodes.some((n) => n.name === 'errores')).toBe(false);
// Outside #263's own `label` kind, no node is named after the label.
expect(withHandlerRows.nodes.some((n) => n.name === 'errores')).toBe(false);
});

it('a module-level `On Error` line alone still creates no module node', () => {
Expand Down
Loading