Skip to content

Commit 11a8e18

Browse files
authored
Merge pull request #1634 from glimmerjs/feature/debug-symbols
Plumb debug symbols when using lexical scope
2 parents 34888e9 + ddfb5f0 commit 11a8e18

File tree

15 files changed

+205
-45
lines changed

15 files changed

+205
-45
lines changed

packages/@glimmer-workspace/integration-tests/lib/compile.ts

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type {
44
Template,
55
TemplateFactory,
66
} from '@glimmer/interfaces';
7-
import type { PrecompileOptions } from '@glimmer/syntax';
7+
import type { PrecompileOptions, PrecompileOptionsWithLexicalScope } from '@glimmer/syntax';
88
import { precompileJSON } from '@glimmer/compiler';
99
import { templateFactory } from '@glimmer/opcode-compiler';
1010

@@ -19,13 +19,17 @@ let templateId = 0;
1919

2020
export function createTemplate(
2121
templateSource: Nullable<string>,
22-
options: PrecompileOptions = {},
22+
options: PrecompileOptions | PrecompileOptionsWithLexicalScope = {},
2323
scopeValues: Record<string, unknown> = {}
2424
): TemplateFactory {
2525
options.locals = options.locals ?? Object.keys(scopeValues ?? {});
2626
let [block, usedLocals] = precompileJSON(templateSource, options);
2727
let reifiedScopeValues = usedLocals.map((key) => scopeValues[key]);
2828

29+
if ('emit' in options && options.emit?.debugSymbols) {
30+
block.push(usedLocals);
31+
}
32+
2933
let templateBlock: SerializedTemplateWithLazyBlock = {
3034
id: String(templateId++),
3135
block: JSON.stringify(block),

packages/@glimmer-workspace/integration-tests/lib/test-helpers/define.ts

+33-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import type {
55
ModifierManager,
66
Owner,
77
} from '@glimmer/interfaces';
8+
import type { PrecompileOptionsWithLexicalScope } from '@glimmer/syntax';
89
import { registerDestructor } from '@glimmer/destroyable';
910
import {
1011
helperCapabilities,
@@ -99,6 +100,33 @@ export interface DefineComponentOptions {
99100

100101
// additional strict-mode keywords
101102
keywords?: string[];
103+
104+
emit?: PrecompileOptionsWithLexicalScope['emit'];
105+
}
106+
107+
export function defComponent(
108+
templateSource: string,
109+
options?: {
110+
component?: object | undefined;
111+
scope?: Record<string, unknown> | undefined;
112+
emit?: {
113+
moduleName?: string;
114+
debugSymbols?: boolean;
115+
};
116+
}
117+
) {
118+
let definition = options?.component ?? templateOnlyComponent();
119+
let templateFactory = createTemplate(
120+
templateSource,
121+
{
122+
strictMode: true,
123+
meta: { moduleName: options?.emit?.moduleName },
124+
emit: { debugSymbols: options?.emit?.debugSymbols ?? true },
125+
},
126+
options?.scope ?? {}
127+
);
128+
setComponentTemplate(templateFactory, definition);
129+
return definition;
102130
}
103131

104132
export function defineComponent(
@@ -116,7 +144,11 @@ export function defineComponent(
116144
let keywords = options.keywords ?? [];
117145

118146
let definition = options.definition ?? templateOnlyComponent();
119-
let templateFactory = createTemplate(templateSource, { strictMode, keywords }, scopeValues ?? {});
147+
let templateFactory = createTemplate(
148+
templateSource,
149+
{ strictMode, keywords, emit: options.emit },
150+
scopeValues ?? {}
151+
);
120152
setComponentTemplate(templateFactory, definition);
121153
return definition;
122154
}

packages/@glimmer-workspace/integration-tests/test/debug-render-tree-test.ts

+87-2
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,15 @@ import type { EmberishCurlyComponent } from '..';
1818
import {
1919
BaseEnv,
2020
createTemplate,
21+
defComponent,
22+
defineSimpleModifier,
2123
GlimmerishComponent,
2224
JitRenderDelegate,
2325
RenderTest,
2426
suite,
2527
test,
2628
tracked,
29+
trackedObj,
2730
} from '..';
2831

2932
interface CapturedBounds {
@@ -74,6 +77,88 @@ class DebugRenderTreeTest extends RenderTest {
7477

7578
declare delegate: DebugRenderTreeDelegate;
7679

80+
@test 'strict-mode components'() {
81+
const state = trackedObj({ showSecond: false });
82+
83+
const HelloWorld = defComponent('{{@arg}}');
84+
const Root = defComponent(
85+
`<HelloWorld @arg="first"/>{{#if state.showSecond}}<HelloWorld @arg="second"/>{{/if}}`,
86+
{ scope: { HelloWorld, state }, emit: { moduleName: 'root.hbs' } }
87+
);
88+
89+
this.renderComponent(Root);
90+
91+
this.assertRenderTree([
92+
{
93+
type: 'component',
94+
name: '{ROOT}',
95+
args: { positional: [], named: {} },
96+
instance: null,
97+
template: 'root.hbs',
98+
bounds: this.elementBounds(this.delegate.getInitialElement()),
99+
children: [
100+
{
101+
type: 'component',
102+
name: 'HelloWorld',
103+
args: { positional: [], named: { arg: 'first' } },
104+
instance: null,
105+
template: '(unknown template module)',
106+
bounds: this.nodeBounds(this.delegate.getInitialElement().firstChild),
107+
children: [],
108+
},
109+
],
110+
},
111+
]);
112+
}
113+
114+
@test 'strict-mode modifiers'() {
115+
const state = trackedObj({ showSecond: false });
116+
117+
const HelloWorld = defComponent('<p ...attributes>{{@arg}}</p>');
118+
const noopFn = () => {};
119+
const noop = defineSimpleModifier(noopFn);
120+
const Root = defComponent(
121+
`<HelloWorld {{noop}} @arg="first"/>{{#if state.showSecond}}<HelloWorld @arg="second"/>{{/if}}`,
122+
{ scope: { HelloWorld, state, noop }, emit: { moduleName: 'root.hbs' } }
123+
);
124+
125+
this.renderComponent(Root);
126+
127+
const element = this.delegate.getInitialElement();
128+
129+
this.assertRenderTree([
130+
{
131+
type: 'component',
132+
name: '{ROOT}',
133+
args: { positional: [], named: {} },
134+
instance: null,
135+
template: 'root.hbs',
136+
bounds: this.elementBounds(element),
137+
children: [
138+
{
139+
type: 'component',
140+
name: 'HelloWorld',
141+
args: { positional: [], named: { arg: 'first' } },
142+
instance: null,
143+
template: '(unknown template module)',
144+
bounds: this.nodeBounds(element.firstChild),
145+
children: [
146+
{
147+
type: 'modifier',
148+
name: 'noop',
149+
args: { positional: [], named: {} },
150+
instance: (modifier: unknown) => modifier && Reflect.get(modifier, 'fn') === noopFn,
151+
template: null,
152+
bounds: this.nodeBounds(element.firstChild),
153+
children: [],
154+
},
155+
],
156+
},
157+
],
158+
},
159+
]);
160+
}
161+
77162
@test 'template-only components'() {
78163
this.registerComponent('TemplateOnly', 'HelloWorld', '{{@arg}}');
79164

@@ -313,7 +398,7 @@ class DebugRenderTreeTest extends RenderTest {
313398
args: { positional: [this.element.firstChild], named: {} },
314399
instance: (instance: GlimmerishComponent) => instance === null,
315400
template: null,
316-
bounds: this.elementBounds(this.element.firstChild! as unknown as Element),
401+
bounds: this.elementBounds(this.element.firstChild! as unknown as SimpleElement),
317402
children: [
318403
{
319404
type: 'component',
@@ -714,7 +799,7 @@ class DebugRenderTreeTest extends RenderTest {
714799
};
715800
}
716801

717-
elementBounds(element: Element): CapturedBounds {
802+
elementBounds(element: SimpleElement): CapturedBounds {
718803
return {
719804
parentElement: element as unknown as SimpleElement,
720805
firstNode: element.firstChild! as unknown as SimpleNode,

packages/@glimmer/compiler/lib/compiler.ts

+4
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ export function precompile(
119119
): TemplateJavascript {
120120
const [block, usedLocals] = precompileJSON(source, options);
121121

122+
if ('emit' in options && options.emit?.debugSymbols && usedLocals.length > 0) {
123+
block.push(usedLocals);
124+
}
125+
122126
const moduleName = options.meta?.moduleName;
123127
const idFn = options.id || defaultId;
124128
const blockJSON = JSON.stringify(block);

packages/@glimmer/interfaces/lib/compile/wire-format/api.d.ts

+8-7
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ import type {
4949
YieldOpcode,
5050
} from './opcodes.js';
5151

52-
export * from './opcodes.js';
53-
export * from './resolution.js';
52+
export type * from './opcodes.js';
53+
export type * from './resolution.js';
5454

5555
export type TupleSyntax = Statement | TupleExpression;
5656

@@ -66,7 +66,7 @@ export type ExpressionSexpOpcodeMap = {
6666
[TSexpOpcode in TupleExpression[0]]: Extract<TupleExpression, { 0: TSexpOpcode }>;
6767
};
6868

69-
export interface SexpOpcodeMap extends ExpressionSexpOpcodeMap, StatementSexpOpcodeMap {}
69+
export interface SexpOpcodeMap extends ExpressionSexpOpcodeMap, StatementSexpOpcodeMap { }
7070
export type SexpOpcode = keyof SexpOpcodeMap;
7171

7272
export namespace Core {
@@ -365,13 +365,14 @@ export type SerializedInlineBlock = [statements: Statements.Statement[], paramet
365365
*/
366366
export type SerializedTemplateBlock = [
367367
// statements
368-
Statements.Statement[],
368+
statements: Statements.Statement[],
369369
// symbols
370-
string[],
370+
symbols: string[],
371371
// hasDebug
372-
boolean,
372+
hasDebug: boolean,
373373
// upvars
374-
string[],
374+
upvars: string[],
375+
lexicalSymbols?: string[]
375376
];
376377

377378
/**

packages/@glimmer/interfaces/lib/components.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export interface ComponentDefinition<
2424
manager: M;
2525
capabilities: CapabilityMask;
2626
compilable: CompilableProgram | null;
27+
debugName?: string | undefined;
2728
}
2829

2930
export interface ComponentInstance<

packages/@glimmer/interfaces/lib/program.d.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -118,12 +118,14 @@ export interface ResolutionTimeConstants {
118118
component(
119119
definitionState: ComponentDefinitionState,
120120
owner: object,
121-
isOptional?: false
121+
isOptional?: false,
122+
debugName?: string
122123
): ComponentDefinition;
123124
component(
124125
definitionState: ComponentDefinitionState,
125126
owner: object,
126-
isOptional?: boolean
127+
isOptional?: boolean,
128+
debugName?: string
127129
): ComponentDefinition | null;
128130

129131
resolvedComponent(

packages/@glimmer/interfaces/lib/template.d.ts

+1
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export interface NamedBlocks {
9696
export interface ContainingMetadata {
9797
evalSymbols: Nullable<string[]>;
9898
upvars: Nullable<string[]>;
99+
debugSymbols?: string[] | undefined;
99100
scopeValues: unknown[] | null;
100101
isStrictMode: boolean;
101102
moduleName: string;

packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/resolution.ts

+15-13
Original file line numberDiff line numberDiff line change
@@ -87,22 +87,23 @@ export function resolveComponent(
8787
assert(!meta.isStrictMode, 'Strict mode errors should already be handled at compile time');
8888

8989
throw new Error(
90-
`Attempted to resolve a component in a strict mode template, but that value was not in scope: ${
91-
meta.upvars![expr[1]] ?? '{unknown variable}'
90+
`Attempted to resolve a component in a strict mode template, but that value was not in scope: ${meta.upvars![expr[1]] ?? '{unknown variable}'
9291
}`
9392
);
9493
}
9594

9695
if (type === SexpOpcodes.GetLexicalSymbol) {
97-
let { scopeValues, owner } = meta;
96+
let { scopeValues, owner, debugSymbols } = meta;
9897
let definition = expect(scopeValues, 'BUG: scopeValues must exist if template symbol is used')[
9998
expr[1]
10099
];
101100

102101
then(
103102
constants.component(
104103
definition as object,
105-
expect(owner, 'BUG: expected owner when resolving component definition')
104+
expect(owner, 'BUG: expected owner when resolving component definition'),
105+
false,
106+
debugSymbols?.at(expr[1])
106107
)
107108
);
108109
} else {
@@ -182,12 +183,12 @@ export function resolveModifier(
182183
let type = expr[0];
183184

184185
if (type === SexpOpcodes.GetLexicalSymbol) {
185-
let { scopeValues } = meta;
186+
let { scopeValues, debugSymbols } = meta;
186187
let definition = expect(scopeValues, 'BUG: scopeValues must exist if template symbol is used')[
187188
expr[1]
188189
];
189190

190-
then(constants.modifier(definition as object));
191+
then(constants.modifier(definition as object, debugSymbols?.at(expr[1]) ?? undefined));
191192
} else if (type === SexpOpcodes.GetStrictKeyword) {
192193
let { upvars } = assertResolverInvariants(meta);
193194
let name = unwrap(upvars[expr[1]]);
@@ -215,7 +216,7 @@ export function resolveModifier(
215216
);
216217
}
217218

218-
then(constants.modifier(modifier, name));
219+
then(constants.modifier(modifier));
219220
}
220221
}
221222

@@ -236,15 +237,16 @@ export function resolveComponentOrHelper(
236237
let type = expr[0];
237238

238239
if (type === SexpOpcodes.GetLexicalSymbol) {
239-
let { scopeValues, owner } = meta;
240+
let { scopeValues, owner, debugSymbols } = meta;
240241
let definition = expect(scopeValues, 'BUG: scopeValues must exist if template symbol is used')[
241242
expr[1]
242243
];
243244

244245
let component = constants.component(
245246
definition as object,
246247
expect(owner, 'BUG: expected owner when resolving component definition'),
247-
true
248+
true,
249+
debugSymbols?.at(expr[1])
248250
);
249251

250252
if (component !== null) {
@@ -316,7 +318,7 @@ export function resolveOptionalComponentOrHelper(
316318
let type = expr[0];
317319

318320
if (type === SexpOpcodes.GetLexicalSymbol) {
319-
let { scopeValues, owner } = meta;
321+
let { scopeValues, owner, debugSymbols } = meta;
320322
let definition = expect(scopeValues, 'BUG: scopeValues must exist if template symbol is used')[
321323
expr[1]
322324
];
@@ -333,7 +335,8 @@ export function resolveOptionalComponentOrHelper(
333335
let component = constants.component(
334336
definition,
335337
expect(owner, 'BUG: expected owner when resolving component definition'),
336-
true
338+
true,
339+
debugSymbols?.at(expr[1])
337340
);
338341

339342
if (component !== null) {
@@ -390,8 +393,7 @@ function lookupBuiltInHelper(
390393
// Keyword helper did not exist, which means that we're attempting to use a
391394
// value of some kind that is not in scope
392395
throw new Error(
393-
`Attempted to resolve a ${type} in a strict mode template, but that value was not in scope: ${
394-
meta.upvars![expr[1]] ?? '{unknown variable}'
396+
`Attempted to resolve a ${type} in a strict mode template, but that value was not in scope: ${meta.upvars![expr[1]] ?? '{unknown variable}'
395397
}`
396398
);
397399
}

0 commit comments

Comments
 (0)