Skip to content
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

do not leave emu-import / emu-biblio in the generated DOM #624

Merged
merged 4 commits into from
Oct 30, 2024
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
2 changes: 1 addition & 1 deletion src/Context.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type Spec from './Spec';
import type Import from './Import';
import type { Import } from './Import';
import type Clause from './Clause';
import type { ClauseNumberIterator } from './clauseNums';

Expand Down
96 changes: 38 additions & 58 deletions src/Import.ts
Original file line number Diff line number Diff line change
@@ -1,84 +1,64 @@
import type Spec from './Spec';

import Builder from './Builder';
import * as utils from './utils';
import * as path from 'path';
import type { JSDOM } from 'jsdom';

export default class Import extends Builder {
/** @internal */ public importLocation: string;
/** @internal */ public relativeRoot: string;
/** @internal */ public source: string;
export type Import = {
importLocation: string;
relativeRoot: string;
source: string;
};

constructor(
spec: Spec,
node: HTMLElement,
importLocation: string,
relativeRoot: string,
source: string,
) {
super(spec, node);
this.importLocation = importLocation;
this.relativeRoot = relativeRoot;
this.source = source;
}
export async function buildImports(spec: Spec, importNode: EmuImportElement, root: string) {
const href = importNode.getAttribute('href');
if (!href) throw new Error('Import missing href attribute.');
const importPath = path.join(root, href);
const relativeRoot = path.dirname(importPath);

static async build(spec: Spec, node: EmuImportElement, root: string) {
const href = node.getAttribute('href');
if (!href) throw new Error('Import missing href attribute.');
const importPath = path.join(root, href);
const relativeRoot = path.dirname(importPath);
const html = await spec.fetch(importPath);

const html = await spec.fetch(importPath);
spec.imports.push({ importLocation: importPath, relativeRoot, source: html });

const imp = new Import(spec, node, importPath, relativeRoot, html);
spec.imports.push(imp);
const importDom = utils.htmlToDom(html);
importNode.dom = importDom;
importNode.source = html;
importNode.importPath = importPath;

const importDom = utils.htmlToDom(html);
node.dom = importDom;
node.source = html;
node.importPath = importPath;
const importDoc = importDom.window.document;

const importDoc = importDom.window.document;
const nodes = importDoc.body.childNodes;
const frag = spec.doc.createDocumentFragment();
// clone this list so we can walk it after the replaceWith call
const importedNodes = [...importDoc.body.childNodes];
const frag = spec.doc.createDocumentFragment();

for (let i = 0; i < nodes.length; i++) {
const node = nodes[i];
const importedNode = spec.doc.adoptNode(node);
frag.appendChild(importedNode);
}
for (let i = 0; i < importedNodes.length; i++) {
const importedNode = spec.doc.adoptNode(importedNodes[i]);
importedNodes[i] = importedNode;
frag.appendChild(importedNode);

const children = frag.childElementCount;
node.appendChild(frag);
spec.topLevelImportedNodes.set(importedNode, importNode);
}

// This is a bit gross.
// We want to do this check after adopting the elements into the main DOM, so the location-finding infrastructure works
// But `appendChild(documentFragment)` both empties out the original fragment and returns it.
// So we have to remember how many child elements we are adding and walk over each of them manually.
for (let i = node.children.length - children; i < node.children.length; ++i) {
const child = node.children[i];
const biblios = [
...child.querySelectorAll('emu-biblio'),
...(child.tagName === 'EMU-BIBLIO' ? [child] : []),
importNode.replaceWith(frag);

for (let i = 0; i < importedNodes.length; i++) {
const importedNode = importedNodes[i];
if (importedNode.nodeType === 1 /* Node.ELEMENT_NODE */) {
const importedImports = [
...(importedNode as HTMLElement).querySelectorAll('emu-import'),
// we have to do this because querySelectorAll can't return its `this`
...((importedNode as HTMLElement).tagName === 'EMU-IMPORT' ? [importedNode] : []),
];
for (const biblio of biblios) {
spec.warn({
type: 'node',
node: biblio,
ruleId: 'biblio-in-import',
message: 'emu-biblio elements cannot be used within emu-imports',
});
for (const childImport of importedImports) {
await buildImports(spec, childImport as EmuImportElement, relativeRoot);
}
}

return imp;
}
}

export interface EmuImportElement extends HTMLElement {
href: string;
dom?: JSDOM;
dom: JSDOM;
source?: string;
importPath?: string;
}
63 changes: 37 additions & 26 deletions src/Spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ import * as yaml from 'js-yaml';
import * as utils from './utils';
import * as hljs from 'highlight.js';
// Builders
import type { EmuImportElement } from './Import';
import Import from './Import';
import { buildImports } from './Import';
import type { Import, EmuImportElement } from './Import';
import Clause from './Clause';
import ClauseNumbers from './clauseNums';
import Algorithm from './Algorithm';
Expand Down Expand Up @@ -273,10 +273,6 @@ function wrapWarn(source: string, spec: Spec, warn: (err: EcmarkupError) => void
};
}

function isEmuImportElement(node: Node): node is EmuImportElement {
return node.nodeType === 1 && node.nodeName === 'EMU-IMPORT';
}

export type WorklistItem = { aoid: string | null; effects: string[] };
export function maybeAddClauseToEffectWorklist(
effectName: string,
Expand Down Expand Up @@ -339,6 +335,7 @@ export default class Spec {
/** @internal */ _emuMetasToRender: Set<HTMLElement>;
/** @internal */ _emuMetasToRemove: Set<HTMLElement>;
/** @internal */ refsByClause: { [refId: string]: [string] };
/** @internal */ topLevelImportedNodes: Map<Node, EmuImportElement>;

private _fetch: (file: string, token: CancellationToken) => PromiseLike<string>;

Expand Down Expand Up @@ -384,6 +381,7 @@ export default class Spec {
this._emuMetasToRender = new Set();
this._emuMetasToRemove = new Set();
this.refsByClause = Object.create(null);
this.topLevelImportedNodes = new Map();

this.processMetadata();
Object.assign(this.opts, opts);
Expand Down Expand Up @@ -653,15 +651,22 @@ export default class Spec {
node: Element | Node,
): ({ file?: string; source: string } & ElementLocation) | undefined {
let pointer: Element | Node | null = node;
while (pointer != null) {
if (isEmuImportElement(pointer)) {
break;
let dom: JSDOM;
let file: string | undefined;
let source: string | undefined;
search: {
while (pointer != null) {
if (this.topLevelImportedNodes.has(pointer)) {
const importNode = this.topLevelImportedNodes.get(pointer)!;
dom = importNode.dom;
file = importNode.importPath;
source = importNode.source;
break search;
}
pointer = pointer.parentElement;
}
pointer = pointer.parentElement;
}
const dom = pointer == null ? this.dom : pointer.dom;
if (!dom) {
return;
// else
dom = this.dom;
}
const loc = dom.nodeLocation(node);
if (loc) {
Expand All @@ -679,8 +684,8 @@ export default class Spec {
endCol: loc.endCol,
};
if (pointer != null) {
out.file = pointer.importPath!;
out.source = pointer.source!;
out.file = file!;
out.source = source!;
}
return out;
}
Expand Down Expand Up @@ -1266,6 +1271,7 @@ ${this.opts.multipage ? `<li><span>Navigate to/from multipage</span><code>m</cod
const biblioPaths = [];
for (const biblioEle of this.doc.querySelectorAll('emu-biblio')) {
const href = biblioEle.getAttribute('href');
biblioEle.remove();
if (href == null) {
this.spec.warn({
type: 'node',
Expand Down Expand Up @@ -1305,7 +1311,21 @@ ${this.opts.multipage ? `<li><span>Navigate to/from multipage</span><code>m</cod
}

private async loadImports() {
await loadImports(this, this.spec.doc.body, this.rootDir);
const imports = this.doc.body.querySelectorAll('EMU-IMPORT');
for (let i = 0; i < imports.length; i++) {
await buildImports(this, imports[i] as EmuImportElement, this.rootDir);
}

// we've already removed biblio elements in the main document in loadBiblios
// so any which are here now were in an import, which is illegal
for (const biblioEle of this.doc.querySelectorAll('emu-biblio')) {
this.warn({
type: 'node',
node: biblioEle,
ruleId: 'biblio-in-import',
message: 'emu-biblio elements cannot be used within emu-imports',
});
}
}

public exportBiblio(): ExportedBiblio | null {
Expand Down Expand Up @@ -1895,15 +1915,6 @@ function getBoilerplate(file: string) {
return fs.readFileSync(boilerplateFile, 'utf8');
}

async function loadImports(spec: Spec, rootElement: HTMLElement, rootPath: string) {
const imports = rootElement.querySelectorAll('EMU-IMPORT');
for (let i = 0; i < imports.length; i++) {
const node = imports[i];
const imp = await Import.build(spec, node as EmuImportElement, rootPath);
await loadImports(spec, node as HTMLElement, imp.relativeRoot);
}
}

async function walk(walker: TreeWalker, context: Context) {
const previousInNoAutolink = context.inNoAutolink;
let previousInNoEmd = context.inNoEmd;
Expand Down
2 changes: 1 addition & 1 deletion src/lint/collect-spelling-diagnostics.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Warning } from '../Spec';
import type { default as Import } from '../Import';
import type { Import } from '../Import';

import { offsetToLineAndColumn } from '../utils';

Expand Down
2 changes: 1 addition & 1 deletion test/baselines/generated-reference/algorithms.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<li><span>Toggle pinning of the current clause</span><code>p</code></li>
<li><span>Jump to <i>n</i>th pin</span><code>1-9</code></li>
</ul></div><div id="spec-container">
<emu-biblio href="./algorithmsBiblio.json"></emu-biblio>

<emu-alg><ol><li>Can call <emu-xref href="#sec-algorithm-conventions-abstract-operations"><a href="https://tc39.es/ecma262/#sec-algorithm-conventions-abstract-operations">abstract operations</a></emu-xref> in this spec: <emu-xref aoid="Internal" id="_ref_0"><a href="#sec-internal">Internal</a></emu-xref>();</li><li>Can call <emu-xref href="#sec-algorithm-conventions-abstract-operations"><a href="https://tc39.es/ecma262/#sec-algorithm-conventions-abstract-operations">abstract operations</a></emu-xref> in ES6: <emu-xref aoid="ReturnIfAbrupt"><a href="https://tc39.es/ecma262/#sec-returnifabrupt">ReturnIfAbrupt</a></emu-xref>(<var>completion</var>);</li><li>Can call <emu-xref href="#sec-algorithm-conventions-abstract-operations"><a href="https://tc39.es/ecma262/#sec-algorithm-conventions-abstract-operations">abstract operations</a></emu-xref> in a biblio file: <emu-xref aoid="Biblio"><a href="http://example.com/fooSite.html#sec-biblio">Biblio</a></emu-xref>();</li><li>Unfound <emu-xref href="#sec-algorithm-conventions-abstract-operations"><a href="https://tc39.es/ecma262/#sec-algorithm-conventions-abstract-operations">abstract operations</a></emu-xref> just don't link: Unfound();</li><li>Can prefix with !&nbsp;and ?.<ol><li>Let <var>foo</var> be ?&nbsp;<emu-xref aoid="Internal" id="_ref_1"><a href="#sec-internal">Internal</a></emu-xref>();</li><li>Set <var>foo</var> to !&nbsp;<emu-xref aoid="Internal" id="_ref_2"><a href="#sec-internal">Internal</a></emu-xref>();</li><li>Set <var>foo</var> to !&nbsp;SDO of <var>operation</var>.</li><li>Set <var>foo</var> to !&nbsp;<var>operation</var>.<var class="field">[[MOP]]</var>().</li></ol></li><li>A <emu-xref href="#sec-list-and-record-specification-type"><a href="https://tc39.es/ecma262/#sec-list-and-record-specification-type">Record</a></emu-xref> looks like this: <emu-xref href="#sec-list-and-record-specification-type"><a href="https://tc39.es/ecma262/#sec-list-and-record-specification-type">Record</a></emu-xref> { <var class="field">[[Key]]</var>: 0&nbsp;}.</li><li>A <emu-xref href="#sec-list-and-record-specification-type"><a href="https://tc39.es/ecma262/#sec-list-and-record-specification-type">List</a></emu-xref> looks like this: « 0, 1&nbsp;».</li></ol></emu-alg>

<emu-clause id="sec-internal" aoid="Internal">
Expand Down
15 changes: 9 additions & 6 deletions test/baselines/generated-reference/imports.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,26 @@
<li><span>Toggle pinning of the current clause</span><code>p</code></li>
<li><span>Jump to <i>n</i>th pin</span><code>1-9</code></li>
</ul></div><div id="spec-container">
<emu-import href="imports/import1.html"><emu-clause id="Baz" aoid="Baz">
<emu-clause id="Baz" aoid="Baz">
<h1><span class="secnum">1</span> Header</h1>
<p>text</p>
<p><emu-nt>nonterminal</emu-nt> <code>code</code> <emu-val>value</emu-val>.</p>

<emu-import href="sub/import3.html"><emu-clause id="import3">
<emu-clause id="import3">
<h1><span class="secnum">1.1</span> Import 3</h1>
wtf??
<emu-grammar type="definition"><emu-production name="A" collapsed="" id="prod-A">
<emu-nt><a href="#prod-A">A</a></emu-nt> <emu-geq>:</emu-geq> <emu-rhs a="ayxoiyyq"><emu-t>b</emu-t></emu-rhs>
</emu-production>
</emu-grammar>
</emu-clause></emu-import>
</emu-clause></emu-import>
<emu-import href="imports/import2.html"><emu-clause id="import2">
</emu-clause>

</emu-clause>

<emu-clause id="import2">
<h1><span class="secnum">2</span> Import 2</h1>
</emu-clause></emu-import>
</emu-clause>


<emu-alg><ol><li>Ensure we can auto-link to imported aoids: <emu-xref aoid="Baz" id="_ref_0"><a href="#Baz">Baz</a></emu-xref>()</li></ol></emu-alg>
</div></body>
10 changes: 6 additions & 4 deletions test/baselines/generated-reference/test.html
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,22 @@ <h1><span class="secnum">1.1</span> Sub Clause</h1>
<h1><span class="secnum">1.2</span> Sub Clause</h1>
</emu-clause>

<emu-import href="imports/import1.html"><emu-clause id="Baz" aoid="Baz">
<emu-clause id="Baz" aoid="Baz">
<h1><span class="secnum">1.3</span> Header</h1>
<p>text</p>
<p><emu-nt>nonterminal</emu-nt> <code>code</code> <emu-val>value</emu-val>.</p>

<emu-import href="sub/import3.html"><emu-clause id="import3">
<emu-clause id="import3">
<h1><span class="secnum">1.3.1</span> Import 3</h1>
wtf??
<emu-grammar type="definition"><emu-production name="A" collapsed="" id="prod-A">
<emu-nt><a href="#prod-A">A</a></emu-nt> <emu-geq>:</emu-geq> <emu-rhs a="ayxoiyyq"><emu-t>b</emu-t></emu-rhs>
</emu-production>
</emu-grammar>
</emu-clause></emu-import>
</emu-clause></emu-import>
</emu-clause>

</emu-clause>


<emu-alg><ol><li>Call <emu-xref aoid="Foo" id="_ref_3"><a href="#Foo">Foo</a></emu-xref>(<var>a</var>).</li><li>Call <emu-xref aoid="Bar" id="_ref_4"><a href="#Bar">Bar</a></emu-xref>(<code>toString</code>).</li><li>Call <emu-xref aoid="Baz" id="_ref_5"><a href="#Baz">Baz</a></emu-xref>(<emu-val>true</emu-val>).<ol><li>Do something else.</li><li>And again.</li></ol></li></ol></emu-alg>

Expand Down
2 changes: 1 addition & 1 deletion test/baselines/generated-reference/xref.html
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<li><span>Toggle pinning of the current clause</span><code>p</code></li>
<li><span>Jump to <i>n</i>th pin</span><code>1-9</code></li>
</ul></div><div id="spec-container">
<emu-biblio href="./xrefTestBiblio.json"></emu-biblio>

<emu-clause id="sec-clause-id">
<h1><span class="secnum">1</span> Clause Title</h1>

Expand Down
Loading