Skip to content

Commit

Permalink
Fixes to user code effect handling (#385)
Browse files Browse the repository at this point in the history
  • Loading branch information
syg authored Dec 10, 2021
1 parent 1de7eeb commit a8f57a6
Show file tree
Hide file tree
Showing 5 changed files with 119 additions and 37 deletions.
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>

0 comments on commit a8f57a6

Please sign in to comment.