Skip to content

Give each template a function scope - #261

Merged
NullVoxPopuli merged 1 commit into
ember-tooling:mainfrom
aklkv:fix/template-function-scope
Oct 9, 2026
Merged

NullVoxPopuli merged 1 commit into
ember-tooling:mainfrom
aklkv:fix/template-function-scope

Conversation

@aklkv

@aklkv aklkv commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Follow-up to #256. ESLint's own no-use-before-define reports a component that renders itself:

const Tree = <template>{{#if @child}}<Tree @node={{@child}} />{{/if}}</template>;
//                                     ^^^^ 'Tree' was used before it was defined.

class Branch extends Component {
  <template>{{#if @child}}<Branch @node={{@child}} />{{/if}}</template>
  //                        ^^^^^^ 'Branch' was used before it was defined.
}

It does this on every release, for .gjs and .gts, and even with eslint-config-standard's { functions: false, classes: false, variables: false }. The only way out is turning the rule off. The rule exempts a reference only when it runs in a separate execution context (variable.scope.variableScope !== reference.from.variableScope), and the block scope #256 gives each template shares its upper scope's variableScope.

Fix

A template compiles to a closure (template(…, { scope: () => ({ Tree }) })), so each template now gets a function scope instead of a block scope. Block params keep their block scopes inside it.

  • TypeScript path: a TemplateScope subclass of scope-manager's FunctionScope.
    • It passes isMethodDefinition so the scope is strict without the "use strict" directive scan, which throws on a template's body when the enclosing scope isn't strict (a script, which is also ESLint's default without sourceType).
    • It removes the implicit arguments that FunctionScope declares, so {{arguments}} stays undefined.
    • Same constructor in scope-manager 6, 7 and 8.
  • eslint-scope (Babel) path: the base Scope with type function, which declares nothing, with the same strictness argument.

Behaviour changes

  • Core no-use-before-define no longer reports a template's reference to its own component (const, class, or static class field), with default or standard options.
  • With variables: false, both core and typescript-eslint's no-use-before-define now exempt a template reading a variable declared later, as they do for an arrow function. With default options it's still reported.
  • no-var keeps reporting a loop var that a template reads, but no longer autofixes it, as for a closure.
  • require-atomic-updates treats a variable a template reads as escaping its function, as for a closure.

Tests

Also run: the full suite, lint, the test projects, eslint-plugin-ember's suite (scripts/eslint-plugin-ember-test.mjs), the CI matrix's ^6/^7 overrides, and a lint comparison against #256's block scope over the repo's 48 .gjs/.gts files plus self-reference samples (TS, TS script mode, Babel) with 14 scope-sensitive rules, which differs only in the intended no-use-before-define reports.

ESLint's no-use-before-define reports a component that renders itself:

    const Foo = <template><Foo /></template>;
    class Bar extends Component { <template><Bar /></template> }

It does so even with `{ functions: false, classes: false, variables:
false }` (eslint-config-standard), because it only exempts references from
a separate execution context, and a template's block scope shares its
upper scope's. A template compiles to a closure (`scope: () => ({ Foo })`),
so give it a function scope instead.

On the TypeScript path, FunctionScope declares an implicit `arguments` and
scans the body for a "use strict" directive, which throws on a template's
body when the upper scope isn't strict (a script). A TemplateScope
subclass removes `arguments` and passes `isMethodDefinition` to skip the
scan; the eslint-scope path uses the base Scope, which declares nothing.
@NullVoxPopuli NullVoxPopuli added the bug Something isn't working label Oct 9, 2026
@github-actions

github-actions Bot commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

🏎️ Benchmark Comparison

Parse

Benchmark Control (p50) Experiment (p50) Δ
🟢 gts small 770.16 ms 748.57 ms -2.8%
⚪ gts medium 1283.82 ms 1291.50 ms +0.6%
⚪ gts large 599.32 ms 598.87 ms -0.1%
⚪ gjs small 633.64 ms 635.38 ms +0.3%
⚪ gjs medium 1084.91 ms 1097.30 ms +1.1%
⚪ gjs large 495.94 ms 495.65 ms -0.1%
🟠 hbs small 86.99 ms 88.96 ms +2.3%
🟢 hbs medium 581.64 ms 561.75 ms -3.4%
🟢 hbs large 646.46 ms 627.60 ms -2.9%

🟢 faster · 🔴 slower · 🟠 slightly slower · ⚪ within 2%

Full mitata output
clk: ~3.51 GHz
cpu: AMD EPYC 9V74 80-Core Processor
runtime: node 24.21.0 (x64-linux)

benchmark                   avg (min … max) p75 / p99    (min … top 1%)
------------------------------------------- -------------------------------
gts small (control)          792.21 ms/iter 825.06 ms    █    █            
                       (675.22 ms … 1.01 s) 922.33 ms ▅▅ █▅   █  ▅▅  ▅    ▅
                    ( 14.81 mb …  90.95 mb)  34.49 mb ██▁██▁▁▁█▁▁██▁▁█▁▁▁▁█

gts small (experiment)       776.80 ms/iter 794.73 ms     █                
                       (668.85 ms … 1.01 s) 898.14 ms ▅ ▅▅█  ▅▅▅ ▅    ▅   ▅
                    ( 14.63 mb …  90.89 mb)  34.48 mb █▁███▁▁███▁█▁▁▁▁█▁▁▁█

                             ┌                                            ┐
                              ╷      ┌─────────────┬─────┐                ╷
         gts small (control)  ├──────┤             │     ├────────────────┤
                              ╵      └─────────────┴─────┘                ╵
                             ╷     ┌────────────┬──┐                  ╷
      gts small (experiment) ├─────┤            │  ├──────────────────┤
                             ╵     └────────────┴──┘                  ╵
                             └                                            ┘
                             668.85 ms         795.59 ms          922.33 ms

summary
  gts small (experiment)
   1.02x faster than gts small (control)

------------------------------------------- -------------------------------
gts medium (control)            1.31 s/iter    1.32 s █  ████   ██ █ █ █  █
                          (1.20 s … 1.69 s)    1.37 s █  ████   ██ █ █ █  █
                    ( 83.64 mb … 102.68 mb)  87.34 mb █▁▁████▁▁▁██▁█▁█▁█▁▁█

gts medium (experiment)         1.32 s/iter    1.35 s    █       █         
                          (1.20 s … 1.71 s)    1.38 s ▅ ▅█  ▅   ▅█     ▅▅ ▅
                    ( 83.95 mb … 101.81 mb)  87.89 mb █▁██▁▁█▁▁▁██▁▁▁▁▁██▁█

                             ┌                                            ┐
                             ╷        ┌───────────────────┬──┐          ╷
        gts medium (control) ├────────┤                   │  ├──────────┤
                             ╵        └───────────────────┴──┘          ╵
                             ╷       ┌─────────────────────┬───────┐      ╷
     gts medium (experiment) ├───────┤                     │       ├──────┤
                             ╵       └─────────────────────┴───────┘      ╵
                             └                                            ┘
                             1.20 s              1.29 s              1.38 s

summary
  gts medium (control)
   1x faster than gts medium (experiment)

------------------------------------------- -------------------------------
gts large (control)          641.54 ms/iter 647.22 ms    █  █              
                    (575.95 ms … 963.67 ms) 683.28 ms ▅▅▅█▅ █      ▅  ▅   ▅
                    ( 15.66 mb …  87.62 mb)  34.61 mb █████▁█▁▁▁▁▁▁█▁▁█▁▁▁█

gts large (experiment)       642.62 ms/iter 636.30 ms   █                  
                    (584.04 ms … 908.00 ms) 715.83 ms  ▅█                  
                    ( 17.32 mb …  87.56 mb)  35.35 mb ▇██▇▁▁▁▁▇▁▁▁▁▁▁▁▁▇▁▁▇

                             ┌                                            ┐
                             ╷  ┌─────────────────┬─┐           ╷
         gts large (control) ├──┤                 │ ├───────────┤
                             ╵  └─────────────────┴─┘           ╵
                                ╷ ┌───────────────┬                       ╷
      gts large (experiment)    ├─┤               │───────────────────────┤
                                ╵ └───────────────┴                       ╵
                             └                                            ┘
                             575.95 ms         645.89 ms          715.83 ms

summary
  gts large (control)
   1x faster than gts large (experiment)

------------------------------------------- -------------------------------
gjs small (control)          654.46 ms/iter 657.14 ms █     █              
                    (595.22 ms … 892.77 ms) 726.49 ms █ █   █              
                    ( 28.26 mb …  47.77 mb)  34.15 mb █▁█▁▁▁█▁▁█▁█▁▁▁▁▁▁▁▁█

gjs small (experiment)       658.02 ms/iter 667.30 ms    █  █              
                    (598.67 ms … 879.18 ms) 720.29 ms ▅▅▅█  █▅   ▅▅       ▅
                    ( 32.06 mb …  48.07 mb)  34.55 mb ████▁▁██▁▁▁██▁▁▁▁▁▁▁█

                             ┌                                            ┐
                             ╷┌──────────────────┬┐                       ╷
         gjs small (control) ├┤                  │├───────────────────────┤
                             ╵└──────────────────┴┘                       ╵
                              ╷    ┌───────────────┬──┐                 ╷
      gjs small (experiment)  ├────┤               │  ├─────────────────┤
                              ╵    └───────────────┴──┘                 ╵
                             └                                            ┘
                             595.22 ms         660.85 ms          726.49 ms

summary
  gjs small (control)
   1.01x faster than gjs small (experiment)

------------------------------------------- -------------------------------
gjs medium (control)            1.12 s/iter    1.14 s    █              █  
                          (1.04 s … 1.43 s)    1.15 s ▅ ▅█  ▅ ▅ ▅   ▅   █ ▅
                    ( 20.85 mb …  93.29 mb)  72.96 mb █▁██▁▁█▁█▁█▁▁▁█▁▁▁█▁█

gjs medium (experiment)         1.13 s/iter    1.15 s      █               
                          (1.04 s … 1.43 s)    1.16 s      █              █
                    ( 21.07 mb …  93.91 mb)  67.86 mb █▁▁▁▁█▁▁██▁█▁█▁▁▁▁█▁█

                             ┌                                            ┐
                             ╷    ┌───────────────────────┬────────┐  ╷
        gjs medium (control) ├────┤                       │        ├──┤
                             ╵    └───────────────────────┴────────┘  ╵
                             ╷          ┌─────────────────────┬───────┐   ╷
     gjs medium (experiment) ├──────────┤                     │       ├───┤
                             ╵          └─────────────────────┴───────┘   ╵
                             └                                            ┘
                             1.04 s              1.10 s              1.16 s

summary
  gjs medium (control)
   1.01x faster than gjs medium (experiment)

------------------------------------------- -------------------------------
gjs large (control)          527.34 ms/iter 503.04 ms   ██                 
                    (482.74 ms … 788.35 ms) 581.42 ms   ██                 
                    (416.56 kb …  70.90 mb)  40.20 mb █████▁▁█▁▁▁▁▁▁▁▁▁▁▁▁█

gjs large (experiment)       525.94 ms/iter 515.47 ms    █                 
                    (478.12 ms … 738.81 ms) 583.34 ms   ▅█                 
                    (401.51 kb …  70.94 mb)  40.27 mb ▇▁██▁▁▇▇▁▇▁▁▁▁▁▁▁▁▁▁▇

                             ┌                                            ┐
                               ╷   ┌──────────────┬                      ╷
         gjs large (control)   ├───┤              │──────────────────────┤
                               ╵   └──────────────┴                      ╵
                             ╷     ┌─────────────┬                        ╷
      gjs large (experiment) ├─────┤             │────────────────────────┤
                             ╵     └─────────────┴                        ╵
                             └                                            ┘
                             478.12 ms         530.73 ms          583.34 ms

summary
  gjs large (experiment)
   1x faster than gjs large (control)

------------------------------------------- -------------------------------
hbs small (control)           99.32 ms/iter 104.38 ms     █          █     
                     (72.36 ms … 188.67 ms) 115.56 ms ▅  ▅█ ▅▅▅     ▅█    ▅
                    ( 51.34 mb …  57.02 mb)  54.02 mb █▁▁██▁███▁▁▁▁▁██▁▁▁▁█

hbs small (experiment)       100.90 ms/iter  99.67 ms     █         █      
                     (72.66 ms … 213.39 ms) 110.86 ms ▅  ▅█   ▅▅▅   █  ▅  ▅
                    ( 51.22 mb …  57.29 mb)  53.90 mb █▁▁██▁▁▁███▁▁▁█▁▁█▁▁█

                             ┌                                            ┐
                             ╷        ┌──────────────────┬────┐           ╷
         hbs small (control) ├────────┤                  │    ├───────────┤
                             ╵        └──────────────────┴────┘           ╵
                             ╷        ┌────────────────────┬         ╷
      hbs small (experiment) ├────────┤                    │─────────┤
                             ╵        └────────────────────┴         ╵
                             └                                            ┘
                             72.36 ms          93.96 ms           115.56 ms

summary
  hbs small (control)
   1.02x faster than hbs small (experiment)

------------------------------------------- -------------------------------
hbs medium (control)         616.70 ms/iter 602.31 ms █ █                  
                    (573.35 ms … 836.20 ms) 667.78 ms █ █                  
                    (  9.12 mb …  83.18 mb)  33.18 mb █▁███▁█▁▁▁▁▁▁▁▁▁▁█▁▁█

hbs medium (experiment)      594.69 ms/iter 625.03 ms  ██ █                
                    (551.10 ms … 764.28 ms) 636.32 ms ▅██▅█            ▅ ▅▅
                    (  9.05 mb …  82.87 mb)  33.12 mb █████▁▁▁▁▁▁▁▁▁▁▁▁█▁██

                             ┌                                            ┐
                                      ┌───────────────┬                   ╷
        hbs medium (control)          │               │───────────────────┤
                                      └───────────────┴                   ╵
                             ╷┌───────────────┬───────────┐   ╷
     hbs medium (experiment) ├┤               │           ├───┤
                             ╵└───────────────┴───────────┘   ╵
                             └                                            ┘
                             551.10 ms         609.44 ms          667.78 ms

summary
  hbs medium (experiment)
   1.04x faster than hbs medium (control)

------------------------------------------- -------------------------------
hbs large (control)          667.72 ms/iter 650.55 ms  █                   
                    (641.52 ms … 825.44 ms) 722.33 ms  █                   
                    ( 53.44 mb …  64.87 mb)  55.52 mb ██▅▅▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▅

hbs large (experiment)       649.30 ms/iter 635.90 ms   █                  
                    (622.46 ms … 824.39 ms) 675.35 ms   █                  
                    ( 53.47 mb …  64.88 mb)  55.51 mb ▆▆█▆▁▆▁▆▁▁▁▁▁▁▁▁▁▁▁▁▆

                             ┌                                            ┐
                                      ╷┌─────────┬                        ╷
         hbs large (control)          ├┤         │────────────────────────┤
                                      ╵└─────────┴                        ╵
                             ╷ ┌─────────┬           ╷
      hbs large (experiment) ├─┤         │───────────┤
                             ╵ └─────────┴           ╵
                             └                                            ┘
                             622.46 ms         672.39 ms          722.33 ms

summary
  hbs large (experiment)
   1.03x faster than hbs large (control)
### Project mode (type-aware)
Benchmark Control (p50) Experiment (p50) Δ
🟢 project warm parse x150 501.51 ms 488.44 ms -2.6%
⚪ project parse+typed x150 517.68 ms 510.21 ms -1.4%
⚪ projectService warm parse x150 489.24 ms 493.96 ms +1.0%
⚪ projectService parse+typed x150 523.92 ms 521.00 ms -0.6%

🟢 faster · 🔴 slower · 🟠 slightly slower · ⚪ within 2%

Full mitata output
clk: ~3.40 GHz
cpu: AMD EPYC 9V74 80-Core Processor
runtime: node 24.21.0 (x64-linux)

benchmark                                   avg (min … max) p75 / p99    (min … top 1%)
----------------------------------------------------------- -------------------------------
project warm parse x150 (control)            503.99 ms/iter 512.27 ms ███ █ █  █    ██ ██ █
                                    (490.85 ms … 516.67 ms) 515.60 ms ███▁█▁█▁▁█▁▁▁▁██▁██▁█
                                  gc(272.07 ms … 280.73 ms) 113.38 mb (106.22 mb…115.24 mb)

project warm parse x150 (experiment)         493.78 ms/iter 493.42 ms      █               
                                    (480.70 ms … 524.65 ms) 509.96 ms ▅▁▅▅▁█▁▁▁█▁▁▁▁▅▁▁▁▁▁▅
                                  gc(271.27 ms … 301.66 ms) 112.24 mb (111.84 mb…112.85 mb)

                                             ┌                                            ┐
                                                          ╷  ┌─────────────┬──────────┐   ╷
           project warm parse x150 (control)              ├──┤             │          ├───┤
                                                          ╵  └─────────────┴──────────┘   ╵
                                             ╷    ┌───────────┬                    ╷
        project warm parse x150 (experiment) ├────┤           │────────────────────┤
                                             ╵    └───────────┴                    ╵
                                             └                                            ┘
                                             480.70 ms         498.15 ms          515.60 ms

summary
  project warm parse x150 (experiment)
   1.02x faster than project warm parse x150 (control)

----------------------------------------------------------- -------------------------------
project parse+typed x150 (control)           520.21 ms/iter 527.99 ms █                    
                                    (502.97 ms … 541.31 ms) 539.49 ms █▁▁██▁█▁██▁▁█▁██▁▁▁▁█
                                  gc(272.29 ms … 284.38 ms) 119.50 mb (118.01 mb…123.35 mb)

project parse+typed x150 (experiment)        511.03 ms/iter 517.58 ms             █       █
                                    (487.92 ms … 524.21 ms) 523.06 ms █▁▁▁▁█▁▁▁█▁▁███▁██▁▁█
                                  gc(279.05 ms … 290.38 ms) 112.73 mb (111.39 mb…116.94 mb)

                                             ┌                                            ┐
                                                          ╷     ┌────────┬──────┐         ╷
          project parse+typed x150 (control)              ├─────┤        │      ├─────────┤
                                                          ╵     └────────┴──────┘         ╵
                                             ╷             ┌─────┬─────┐    ╷
       project parse+typed x150 (experiment) ├─────────────┤     │     ├────┤
                                             ╵             └─────┴─────┘    ╵
                                             └                                            ┘
                                             487.92 ms         513.70 ms          539.49 ms

summary
  project parse+typed x150 (experiment)
   1.02x faster than project parse+typed x150 (control)

----------------------------------------------------------- -------------------------------
projectService warm parse x150 (control)     491.68 ms/iter 497.46 ms   █   █             █
                                    (482.27 ms … 500.83 ms) 500.48 ms █▁█▁▁▁█▁█▁▁█▁█▁▁▁█▁▁█
                                  gc(274.13 ms … 286.46 ms) 114.90 mb (111.18 mb…116.16 mb)

projectService warm parse x150 (experiment)  491.67 ms/iter 497.78 ms          █   █       
                                    (476.16 ms … 502.80 ms) 502.76 ms █▁▁█▁█▁▁▁█▁▁▁██▁██▁▁█
                                  gc(276.47 ms … 296.48 ms) 111.10 mb (107.47 mb…112.24 mb)

                                             ┌                                            ┐
                                                       ╷   ┌───────────┬─────────┐    ╷
    projectService warm parse x150 (control)           ├───┤           │         ├────┤
                                                       ╵   └───────────┴─────────┘    ╵
                                             ╷          ┌──────────────┬──────────┐       ╷
 projectService warm parse x150 (experiment) ├──────────┤              │          ├───────┤
                                             ╵          └──────────────┴──────────┘       ╵
                                             └                                            ┘
                                             476.16 ms         489.46 ms          502.76 ms

summary
  projectService warm parse x150 (experiment)
   1x faster than projectService warm parse x150 (control)

----------------------------------------------------------- -------------------------------
projectService parse+typed x150 (control)    525.00 ms/iter 529.25 ms      █        █      
                                    (512.45 ms … 542.26 ms) 536.44 ms █▁█▁██▁▁▁▁██▁▁█▁█▁▁▁█
                                  gc(277.81 ms … 295.71 ms) 119.72 mb (118.82 mb…121.66 mb)

projectService parse+typed x150 (experiment) 527.57 ms/iter 529.24 ms        █             
                                    (501.55 ms … 558.28 ms) 554.92 ms ▅▁▁▁▁▅▁█▅▁█▁▁▁▁▁▅▁▁▁▅
                                  gc(287.74 ms … 375.95 ms) 115.62 mb (111.20 mb…120.89 mb)

                                             ┌                                            ┐
                                                      ╷   ┌──────┬──┐     ╷
   projectService parse+typed x150 (control)          ├───┤      │  ├─────┤
                                                      ╵   └──────┴──┘     ╵
                                             ╷              ┌──────┬┐                     ╷
projectService parse+typed x150 (experiment) ├──────────────┤      │├─────────────────────┤
                                             ╵              └──────┴┘                     ╵
                                             └                                            ┘
                                             501.55 ms         528.24 ms          554.92 ms

summary
  projectService parse+typed x150 (control)
   1x faster than projectService parse+typed x150 (experiment)

@NullVoxPopuli
NullVoxPopuli merged commit dc2b5f9 into ember-tooling:main Oct 9, 2026
40 checks passed
@github-actions github-actions Bot mentioned this pull request Oct 9, 2026
@aklkv
aklkv deleted the fix/template-function-scope branch October 10, 2026 06:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants