Skip to content

Commit 31bc173

Browse files
authored
rephrase algorithm lint rules in terms of parsed lines (#498)
1 parent ba7198b commit 31bc173

12 files changed

+612
-670
lines changed

src/Algorithm.ts

+11-9
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import type { Context } from './Context';
2-
import type { Node as EcmarkdownNode, OrderedListItemNode } from 'ecmarkdown';
2+
import type { Node as EcmarkdownNode, OrderedListItemNode, AlgorithmNode } from 'ecmarkdown';
33
import type { PartialBiblioEntry, StepBiblioEntry } from './Biblio';
44

55
import Builder from './Builder';
@@ -19,6 +19,12 @@ function findLabeledSteps(root: EcmarkdownNode) {
1919
return steps;
2020
}
2121

22+
export type AlgorithmElementWithTree = HTMLElement & {
23+
// null means a failed parse
24+
ecmarkdownTree: AlgorithmNode | null;
25+
originalHtml: string;
26+
};
27+
2228
/*@internal*/
2329
export default class Algorithm extends Builder {
2430
static async enter(context: Context) {
@@ -27,13 +33,14 @@ export default class Algorithm extends Builder {
2733

2834
const innerHTML = node.innerHTML; // TODO use original slice, forward this from linter
2935

30-
let emdTree: ReturnType<typeof emd.parseAlgorithm> | null = null;
36+
let emdTree: AlgorithmNode | null = null;
3137
if ('ecmarkdownTree' in node) {
32-
// @ts-expect-error
33-
emdTree = node.ecmarkdownTree;
38+
emdTree = (node as AlgorithmElementWithTree).ecmarkdownTree;
3439
} else {
3540
try {
3641
emdTree = emd.parseAlgorithm(innerHTML);
42+
(node as AlgorithmElementWithTree).ecmarkdownTree = emdTree;
43+
(node as AlgorithmElementWithTree).originalHtml = innerHTML;
3744
} catch (e) {
3845
warnEmdFailure(spec.warn, node, e as SyntaxError);
3946
}
@@ -43,11 +50,6 @@ export default class Algorithm extends Builder {
4350
return;
4451
}
4552

46-
// @ts-ignore
47-
node.ecmarkdownTree = emdTree;
48-
// @ts-ignore
49-
node.originalHtml = innerHTML;
50-
5153
if (spec.opts.lintSpec && spec.locate(node) != null && !node.hasAttribute('example')) {
5254
const clause = clauseStack[clauseStack.length - 1];
5355
const namespace = clause ? clause.namespace : spec.namespace;

src/Spec.ts

+21-24
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type {
99
import type { Context } from './Context';
1010
import type { AlgorithmBiblioEntry, ExportedBiblio, StepBiblioEntry, Type } from './Biblio';
1111
import type { BuilderInterface } from './Builder';
12+
import type { AlgorithmElementWithTree } from './Algorithm';
1213

1314
import * as path from 'path';
1415
import * as fs from 'fs';
@@ -45,7 +46,7 @@ import {
4546
import { lint } from './lint/lint';
4647
import { CancellationToken } from 'prex';
4748
import type { JSDOM } from 'jsdom';
48-
import type { OrderedListNode, parseAlgorithm } from 'ecmarkdown';
49+
import type { OrderedListNode } from 'ecmarkdown';
4950
import { getProductions, rhsMatches, getLocationInGrammarFile } from './lint/utils';
5051
import type { AugmentedGrammarEle } from './Grammar';
5152
import { offsetToLineAndColumn } from './utils';
@@ -611,29 +612,27 @@ export default class Spec {
611612
if (node.hasAttribute('example') || !('ecmarkdownTree' in node)) {
612613
continue;
613614
}
614-
// @ts-ignore
615-
const tree = node.ecmarkdownTree as ReturnType<typeof parseAlgorithm>;
615+
const tree = (node as AlgorithmElementWithTree).ecmarkdownTree;
616616
if (tree == null) {
617617
continue;
618618
}
619-
// @ts-ignore
620-
const originalHtml: string = node.originalHtml;
619+
const originalHtml = (node as AlgorithmElementWithTree).originalHtml;
621620

622621
const expressionVisitor = (expr: Expr, path: PathItem[]) => {
623622
if (expr.type !== 'call' && expr.type !== 'sdo-call') {
624623
return;
625624
}
626625

627626
const { callee, arguments: args } = expr;
628-
if (!(callee.parts.length === 1 && callee.parts[0].name === 'text')) {
627+
if (!(callee.length === 1 && callee[0].name === 'text')) {
629628
return;
630629
}
631-
const calleeName = callee.parts[0].contents;
630+
const calleeName = callee[0].contents;
632631

633632
const warn = (message: string) => {
634633
const { line, column } = offsetToLineAndColumn(
635634
originalHtml,
636-
callee.parts[0].location.start.offset
635+
callee[0].location.start.offset
637636
);
638637
this.warn({
639638
type: 'contents',
@@ -2099,11 +2098,9 @@ function parentSkippingBlankSpace(expr: Expr, path: PathItem[]): PathItem | null
20992098
parent.type === 'seq' &&
21002099
parent.items.every(
21012100
i =>
2102-
(i.type === 'prose' &&
2103-
i.parts.every(
2104-
p => p.name === 'tag' || (p.name === 'text' && /^\s*$/.test(p.contents))
2105-
)) ||
2106-
i === pointer
2101+
i === pointer ||
2102+
(i.type === 'fragment' &&
2103+
(i.frag.name === 'tag' || (i.frag.name === 'text' && /^\s*$/.test(i.frag.contents))))
21072104
)
21082105
) {
21092106
// if parent is just whitespace/tags around the call, walk up the tree further
@@ -2121,21 +2118,21 @@ function previousText(expr: Expr, path: PathItem[]): string | null {
21212118
}
21222119
const { parent, index } = part;
21232120
if (parent.type === 'seq') {
2124-
return textFromPreviousPart(parent, index as number);
2121+
return textFromPreviousPart(parent, index);
21252122
}
21262123
return null;
21272124
}
21282125

21292126
function textFromPreviousPart(seq: Seq, index: number): string | null {
2130-
const prev = seq.items[index - 1];
2131-
if (prev?.type === 'prose' && prev.parts.length > 0) {
2132-
let prevIndex = prev.parts.length - 1;
2133-
while (prevIndex > 0 && prev.parts[prevIndex].name === 'tag') {
2127+
let prevIndex = index - 1;
2128+
let prev;
2129+
while ((prev = seq.items[prevIndex])?.type === 'fragment') {
2130+
if (prev.frag.name === 'text') {
2131+
return prev.frag.contents;
2132+
} else if (prev.frag.name === 'tag') {
21342133
--prevIndex;
2135-
}
2136-
const prevProse = prev.parts[prevIndex];
2137-
if (prevProse.name === 'text') {
2138-
return prevProse.contents;
2134+
} else {
2135+
break;
21392136
}
21402137
}
21412138
return null;
@@ -2159,13 +2156,13 @@ function isConsumedAsCompletion(expr: Expr, path: PathItem[]) {
21592156
const { parent, index } = part;
21602157
if (parent.type === 'seq') {
21612158
// if the previous text ends in `! ` or `? `, this is a completion
2162-
const text = textFromPreviousPart(parent, index as number);
2159+
const text = textFromPreviousPart(parent, index);
21632160
if (text != null && /[!?]\s$/.test(text)) {
21642161
return true;
21652162
}
21662163
} else if (parent.type === 'call' && index === 0 && parent.arguments.length === 1) {
21672164
// if this is `Completion(Expr())`, this is a completion
2168-
const { parts } = parent.callee;
2165+
const parts = parent.callee;
21692166
if (parts.length === 1 && parts[0].name === 'text' && parts[0].contents === 'Completion') {
21702167
return true;
21712168
}

0 commit comments

Comments
 (0)