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

Fixes to user code effect handling #385

Merged
merged 7 commits into from
Dec 10, 2021
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
8 changes: 7 additions & 1 deletion src/Algorithm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,13 @@ export default class Algorithm extends Builder {
context.inAlg = true;
const { spec, node, clauseStack } = context;

const innerHTML = node.innerHTML; // TODO use original slice, forward this from linter
// Mark all "the result of evaluation Foo" language as having the
// "user-code" effect. Do this before ecmarkdown, otherwise productions like
// |Foo| get turned into tags and the regexp gets complicated.
const innerHTML = node.innerHTML.replace(
/the result of evaluating ([a-zA-Z_|0-9]+)/g,
'the result of <emu-meta effects="user-code">evaluating $1</emu-meta>'
); // TODO use original slice, forward this from linter

let emdTree;
if ('ecmarkdownTree' in node) {
Expand Down
24 changes: 23 additions & 1 deletion src/Meta.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,32 @@ import type { Context } from './Context';

import Builder from './Builder';

import { validateEffects } from './utils';

export default class Meta extends Builder {
static elements = ['EMU-META'];

static async enter({ spec, node }: Context) {
static async enter({ spec, node, clauseStack }: Context) {
const parent = clauseStack[clauseStack.length - 1] || null;
if (node.hasAttribute('effects') && parent !== null) {
const effects = validateEffects(
spec,
node
.getAttribute('effects')!
.split(',')
.map(c => c.trim()),
node
);
for (const effect of effects) {
if (!parent.effects.includes(effect)) {
parent.effects.push(effect);
if (!spec._effectWorklist.has(effect)) {
spec._effectWorklist.set(effect, []);
}
spec._effectWorklist.get(effect)!.push(parent);
}
}
}
spec._emuMetasToRender.add(node);
}

Expand Down
6 changes: 5 additions & 1 deletion src/Xref.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,11 @@ export default class Xref extends Builder {
// Check if there's metadata adding or suppressing effects
this.addEffects = null;
this.suppressEffects = null;
if (node.parentElement && node.parentElement.tagName === 'EMU-META') {
if (
node.parentElement &&
node.parentElement.tagName === 'EMU-META' &&
node.parentElement.children[0] === node
) {
if (node.parentElement.hasAttribute('effects')) {
const addEffects = node.parentElement.getAttribute('effects')!.split(',');
if (addEffects.length !== 0) {
Expand Down
81 changes: 50 additions & 31 deletions test/baselines/generated-reference/effect-user-code.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,102 +4,121 @@
<emu-clause id="sec-user-code" type="abstract operation" aoid="UserCode">
<h1><span class="secnum">1</span> UserCode ( )</h1>
<p>The abstract operation UserCode takes no arguments. It performs the following steps when called:</p>
<emu-alg><ol><li>Call user code.</li></ol></emu-alg>
<emu-alg><ol><li><span class="e-user-code">Call user code</span>.</li></ol></emu-alg>
</emu-clause>


<emu-clause id="sec-user-code-2" type="abstract operation" aoid="UserCode2">
<h1><span class="secnum">2</span> UserCode2 ( )</h1>
<p>The abstract operation UserCode2 takes no arguments.</p>
<p>This AO calls user code despite not being defined with an algorithm.</p>
</emu-clause>

<emu-clause id="sec-nop" type="abstract operation" aoid="Nop">
<h1><span class="secnum">2</span> Nop ( )</h1>
<h1><span class="secnum">3</span> Nop ( )</h1>
<p>The abstract operation Nop takes no arguments. It performs the following steps when called:</p>
<emu-alg><ol><li>Do nothing.</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-direct-call" type="abstract operation" aoid="DirectCall">
<h1><span class="secnum">3</span> DirectCall()</h1>
<h1><span class="secnum">4</span> DirectCall()</h1>
<p>The abstract operation DirectCall takes no arguments. Calling AOs that can call user code should insert <code>e-user-code</code> as a class into the AO link. It performs the following steps when called:</p>
<emu-alg><ol><li><emu-xref aoid="UserCode" id="_ref_0"><a href="#sec-user-code" class="e-user-code">UserCode</a></emu-xref>().</li></ol></emu-alg>
<emu-alg><ol><li><emu-xref aoid="UserCode" id="_ref_0"><a href="#sec-user-code" class="e-user-code">UserCode</a></emu-xref>().</li><li><emu-xref aoid="UserCode2" id="_ref_1"><a href="#sec-user-code-2" class="e-user-code">UserCode2</a></emu-xref>().</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-transitive-call" type="abstract operation" aoid="TransitiveCall">
<h1><span class="secnum">4</span> TransitiveCall()</h1>
<h1><span class="secnum">5</span> TransitiveCall()</h1>
<p>The abstract operation TransitiveCall takes no arguments. Calling AOs that can transitively call user code should insert <code>e-user-code</code> as a class into the AO link. It performs the following steps when called:</p>
<emu-alg><ol><li><emu-xref aoid="DirectCall" id="_ref_1"><a href="#sec-direct-call" class="e-user-code">DirectCall</a></emu-xref>().</li></ol></emu-alg>
<emu-alg><ol><li><emu-xref aoid="DirectCall" id="_ref_2"><a href="#sec-direct-call" class="e-user-code">DirectCall</a></emu-xref>().</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-suppressed-direct-call" type="abstract operation" aoid="SuppressedDirectCall">
<h1><span class="secnum">5</span> SuppressedDirectCall()</h1>
<h1><span class="secnum">6</span> SuppressedDirectCall()</h1>
<p>The abstract operation SuppressedDirectCall takes no arguments. Can-call-user-code callsites that are suppressed do not get <code>e-user-code</code> as a class in the AO link. It performs the following steps when called:</p>
<emu-alg><ol><li><emu-xref aoid="TransitiveCall" id="_ref_2"><a href="#sec-transitive-call">TransitiveCall</a></emu-xref>().</li></ol></emu-alg>
<emu-alg><ol><li><emu-xref aoid="TransitiveCall" id="_ref_3"><a href="#sec-transitive-call">TransitiveCall</a></emu-xref>().</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-suppressed-transitive-call" type="abstract operation" aoid="SuppressedTransitiveCall">
<h1><span class="secnum">6</span> SuppressedTransitiveCall()</h1>
<h1><span class="secnum">7</span> SuppressedTransitiveCall()</h1>
<p>The abstract operation SuppressedTransitiveCall takes no arguments. Can-call-user-code callsites that are suppressed do not propagate the effect It performs the following steps when called:</p>
<emu-alg><ol><li><emu-xref aoid="SuppressedDirectCall" id="_ref_3"><a href="#sec-suppressed-direct-call">SuppressedDirectCall</a></emu-xref>().</li></ol></emu-alg>
<emu-alg><ol><li><emu-xref aoid="SuppressedDirectCall" id="_ref_4"><a href="#sec-suppressed-direct-call">SuppressedDirectCall</a></emu-xref>().</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-added-direct-call" type="abstract operation" aoid="AddedDirectCall">
<h1><span class="secnum">7</span> AddedDirectCall()</h1>
<h1><span class="secnum">8</span> AddedDirectCall()</h1>
<p>The abstract operation AddedDirectCall takes no arguments. AOs can have manually added user-code effect at a callsite that propagates. It performs the following steps when called:</p>
<emu-alg><ol><li><emu-xref aoid="Nop" id="_ref_4"><a href="#sec-nop" class="e-user-code">Nop</a></emu-xref>().</li></ol></emu-alg>
<emu-alg><ol><li><emu-xref aoid="Nop" id="_ref_5"><a href="#sec-nop" class="e-user-code">Nop</a></emu-xref>().</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-added-transitive-call" type="abstract operation" aoid="AddedTransitiveCall">
<h1><span class="secnum">8</span> AddedTransitiveCall()</h1>
<h1><span class="secnum">9</span> AddedTransitiveCall()</h1>
<p>The abstract operation AddedTransitiveCall takes no arguments. AOs can have manually added user-code effect at a callsite that propagates. It performs the following steps when called:</p>
<emu-alg><ol><li><emu-xref aoid="AddedDirectCall" id="_ref_5"><a href="#sec-added-direct-call" class="e-user-code">AddedDirectCall</a></emu-xref>().</li></ol></emu-alg>
<emu-alg><ol><li><emu-xref aoid="AddedDirectCall" id="_ref_6"><a href="#sec-added-direct-call" class="e-user-code">AddedDirectCall</a></emu-xref>().</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-sdo-invocation" type="abstract operation" aoid="SDOInvocations">
<h1><span class="secnum">9</span> SDOInvocations()</h1>
<h1><span class="secnum">10</span> SDOInvocations()</h1>
<p>The abstract operation SDOInvocations takes no arguments. SDO-style invocations of AOs that can call user code also have the <code>e-user-code</code> class in the link. It performs the following steps when called:</p>
<emu-alg><ol><li><emu-xref aoid="UserCode" id="_ref_6"><a href="#sec-user-code" class="e-user-code">UserCode</a></emu-xref> of Bar.</li></ol></emu-alg>
<emu-alg><ol><li><emu-xref aoid="UserCode" id="_ref_7"><a href="#sec-user-code" class="e-user-code">UserCode</a></emu-xref> of Bar.</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-non-invocations" type="abstract operation" aoid="NonInvocations">
<h1><span class="secnum">10</span> NonInvocations()</h1>
<h1><span class="secnum">11</span> NonInvocations()</h1>
<p>The abstract operation NonInvocations takes no arguments. Non-invocations (i.e. not followed by () or " of") do not have <code>e-user-code</code> as a class in the AO link. It performs the following steps when called:</p>
<emu-alg><ol><li><emu-xref aoid="UserCode" id="_ref_7"><a href="#sec-user-code">UserCode</a></emu-xref> is an abstract operation.</li></ol></emu-alg>
<emu-alg><ol><li><emu-xref aoid="UserCode" id="_ref_8"><a href="#sec-user-code">UserCode</a></emu-xref> is an abstract operation.</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-non-abrupt" type="abstract operation" aoid="NonAbrupt">
<h1><span class="secnum">11</span> NonAbrupt()</h1>
<h1><span class="secnum">12</span> NonAbrupt()</h1>
<p>The abstract operation NonAbrupt takes no arguments. Invocations that cannot result in abrupt completions suppress the user-code effect by default. It performs the following steps when called:</p>
<emu-alg><ol><li>Let <var>res</var> be !&nbsp;<emu-xref aoid="UserCode" id="_ref_8"><a href="#sec-user-code">UserCode</a></emu-xref>().</li></ol></emu-alg>
<emu-alg><ol><li>Let <var>res</var> be !&nbsp;<emu-xref aoid="UserCode" id="_ref_9"><a href="#sec-user-code">UserCode</a></emu-xref>().</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-non-abrupt-override" type="abstract operation" aoid="NonAbruptOverride">
<h1><span class="secnum">12</span> NonAbruptOverride()</h1>
<h1><span class="secnum">13</span> NonAbruptOverride()</h1>
<p>The abstract operation NonAbruptOverride takes no arguments. Invocations that cannot result in abrupt completions suppress the user-code effect by default but can still be overridden. It performs the following steps when called:</p>
<emu-alg><ol><li>Let <var>res</var> be !&nbsp;<emu-xref aoid="UserCode" id="_ref_9"><a href="#sec-user-code" class="e-user-code e-user-code">UserCode</a></emu-xref>().</li></ol></emu-alg>
<emu-alg><ol><li>Let <var>res</var> be !&nbsp;<emu-xref aoid="UserCode" id="_ref_10"><a href="#sec-user-code" class="e-user-code e-user-code">UserCode</a></emu-xref>().</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-static-direct-call" type="sdo" aoid="StaticDirectCall">
<h1><span class="secnum">13</span> Static Semantics: StaticDirectCall</h1>
<h1><span class="secnum">14</span> Static Semantics: StaticDirectCall</h1>
<p>The <emu-xref href="#sec-algorithm-conventions-syntax-directed-operations"><a href="https://tc39.es/ecma262/#sec-algorithm-conventions-syntax-directed-operations">syntax-directed operation</a></emu-xref> StaticDirectCall takes no arguments. <emu-xref href="#sec-static-semantic-rules"><a href="https://tc39.es/ecma262/#sec-static-semantic-rules">Static semantics</a></emu-xref> suppress user-code.</p>
<emu-alg><ol><li><emu-xref aoid="UserCode" id="_ref_10"><a href="#sec-user-code">UserCode</a></emu-xref>().</li></ol></emu-alg>
<emu-alg><ol><li><emu-xref aoid="UserCode" id="_ref_11"><a href="#sec-user-code">UserCode</a></emu-xref>().</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-static-transitive-call" type="sdo" aoid="StaticTransitiveCall">
<h1><span class="secnum">14</span> Static Semantics: StaticTransitiveCall</h1>
<h1><span class="secnum">15</span> Static Semantics: StaticTransitiveCall</h1>
<p>The <emu-xref href="#sec-algorithm-conventions-syntax-directed-operations"><a href="https://tc39.es/ecma262/#sec-algorithm-conventions-syntax-directed-operations">syntax-directed operation</a></emu-xref> StaticTransitiveCall takes no arguments. <emu-xref href="#sec-static-semantic-rules"><a href="https://tc39.es/ecma262/#sec-static-semantic-rules">Static semantics</a></emu-xref> suppress user-code.</p>
<emu-alg><ol><li><emu-xref aoid="StaticDirectCall" id="_ref_11"><a href="#sec-static-direct-call">StaticDirectCall</a></emu-xref> of Baz.</li></ol></emu-alg>
<emu-alg><ol><li><emu-xref aoid="StaticDirectCall" id="_ref_12"><a href="#sec-static-direct-call">StaticDirectCall</a></emu-xref> of Baz.</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-rendered-meta" type="abstract operation" aoid="RenderedMeta">
<h1><span class="secnum">15</span> RenderedMeta()</h1>
<h1><span class="secnum">16</span> RenderedMeta()</h1>
<p>The abstract operation RenderedMeta takes no arguments. emu-meta tags with the effects attribute that aren't surrounding what ecmarkup recognizes as invocations are changed into span tags to be rendered. The effects list is prefixed with e- and changed into class names. It performs the following steps when called:</p>
<emu-alg><ol><li>Perform ?&nbsp;<span class="e-user-code"><var>O</var>.[[Call]]()</span>.</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-make-abstract-closure" type="abstract operation" aoid="MakeAbstractClosure">
<h1><span class="secnum">16</span> MakeAbstractClosure()</h1>
<h1><span class="secnum">17</span> MakeAbstractClosure()</h1>
<p>The abstract operation MakeAbstractClosure takes no arguments. The user-code effect doesn't propagate through Abstract Closure boundaries by recognizing the "be a new Abstract Closure" substring. It performs the following steps when called:</p>
<emu-alg><ol><li>Let <var>closure</var> be a new Abstract Closure that captures nothing and performs the following steps when called:<ol><li><emu-xref aoid="UserCode" id="_ref_12"><a href="#sec-user-code" class="e-user-code">UserCode</a></emu-xref>().</li></ol></li><li>Return <var>closure</var>.</li></ol></emu-alg>
<emu-alg><ol><li>Let <var>closure</var> be a new Abstract Closure that captures nothing and performs the following steps when called:<ol><li><emu-xref aoid="UserCode" id="_ref_13"><a href="#sec-user-code" class="e-user-code">UserCode</a></emu-xref>().</li></ol></li><li>Return <var>closure</var>.</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-call-make-abstract-closure" type="abstract operation" aoid="CallMakeAbstractClosure">
<h1><span class="secnum">17</span> CallMakeAbstractClosure()</h1>
<h1><span class="secnum">18</span> CallMakeAbstractClosure()</h1>
<p>The abstract operation CallMakeAbstractClosure takes no arguments. The user-code effect doesn't propagate through Abstract Closure boundaries by recognizing the "be a new Abstract Closure" substring. It performs the following steps when called:</p>
<emu-alg><ol><li><emu-xref aoid="MakeAbstractClosure" id="_ref_13"><a href="#sec-make-abstract-closure">MakeAbstractClosure</a></emu-xref>().</li></ol></emu-alg>
<emu-alg><ol><li><emu-xref aoid="MakeAbstractClosure" id="_ref_14"><a href="#sec-make-abstract-closure">MakeAbstractClosure</a></emu-xref>().</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-xref-not-first-child-of-emu-meta" type="abstract operation" aoid="XrefNotFirstChildOfEmuMeta">
<h1><span class="secnum">19</span> XrefNotFirstChildOfEmuMeta()</h1>
<p>The abstract operation XrefNotFirstChildOfEmuMeta takes no arguments. Effect additions and suppressions via emu-meta only affect xrefs that are the first child of the emu-meta. Below, <emu-xref aoid="UserCode" id="_ref_15"><a href="#sec-user-code" class="e-user-code">UserCode</a></emu-xref>() gets autolinked and get an xref. Its parent element is an emu-meta, but since the xref is not the first child, it should not be interpreted to override the effects of the <emu-xref aoid="UserCode" id="_ref_16"><a href="#sec-user-code" class="e-user-code">UserCode</a></emu-xref>() call. It performs the following steps when called:</p>
<emu-alg><ol><li>Perform <span class="e-user-code"><var>map</var>.[[DefineOwnProperty]](! <emu-xref aoid="UserCode" id="_ref_17"><a href="#sec-user-code">UserCode</a></emu-xref>())</span>.</li></ol></emu-alg>
</emu-clause>

<emu-clause id="sec-result-of-evaluating" type="abstract operation" aoid="ResultOfEvaluating">
<h1><span class="secnum">20</span> ResultOfEvaluating()</h1>
<p>The abstract operation ResultOfEvaluating takes no arguments. The phrase "the result of evaluating Foo" is automatically considered as can call user code. It performs the following steps when called:</p>
<emu-alg><ol><li>Let <var>res</var> be the result of <span class="e-user-code">evaluating <emu-nt>Foo</emu-nt></span>.</li></ol></emu-alg>
</emu-clause>
</div></body>
37 changes: 34 additions & 3 deletions test/baselines/sources/effect-user-code.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,22 @@
<emu-clause id="sec-user-code" type="abstract operation">
<h1>UserCode ( )</h1>
<dl class="header">
<dt>effects</dt>
<dd>user-code</dd>
</dl>
<emu-alg>
1. Call user code.
1. <emu-meta effects="user-code">Call user code</emu-meta>.
</emu-alg>
</emu-clause>


<emu-clause id="sec-user-code-2" type="abstract operation">
<h1>UserCode2 ( )</h1>
<dl class="header">
<dt>effects</dt>
<dd>user-code</dd>
</dl>
<p>This AO calls user code despite not being defined with an algorithm.</p>
</emu-clause>

<emu-clause id="sec-nop" type="abstract operation">
<h1>Nop ( )</h1>
<dl class="header">
Expand All @@ -32,6 +40,7 @@ <h1>DirectCall()</h1>
</dl>
<emu-alg>
1. UserCode().
1. UserCode2().
</emu-alg>
</emu-clause>

Expand Down Expand Up @@ -190,3 +199,25 @@ <h1>CallMakeAbstractClosure()</h1>
1. MakeAbstractClosure().
</emu-alg>
</emu-clause>

<emu-clause id="sec-xref-not-first-child-of-emu-meta" type="abstract operation">
<h1>XrefNotFirstChildOfEmuMeta()</h1>
<dl class="header">
<dt>description</dt>
<dd>Effect additions and suppressions via emu-meta only affect xrefs that are the first child of the emu-meta. Below, UserCode() gets autolinked and get an xref. Its parent element is an emu-meta, but since the xref is not the first child, it should not be interpreted to override the effects of the UserCode() call.</dd>
</dl>
<emu-alg>
1. Perform <emu-meta effects="user-code">_map_.[[DefineOwnProperty]](! UserCode())</emu-meta>.
</emu-alg>
</emu-clause>

<emu-clause id="sec-result-of-evaluating" type="abstract operation">
<h1>ResultOfEvaluating()</h1>
<dl class="header">
<dt>description</dt>
<dd>The phrase "the result of evaluating Foo" is automatically considered as can call user code.</dd>
</dl>
<emu-alg>
1. Let _res_ be the result of evaluating |Foo|.
</emu-alg>
</emu-clause>