Skip to content

Commit fa4c927

Browse files
authored
chore: add flag for extended scope token syntax @W-17710895 (#5211)
* chore: add flag for extended scope token syntax * test(engine-server): remove `feature` feature it's unused * chore: remove feature from types * chore(tests): remove unused exports from tests * chore: rename x/test modules to x/static * chore: use static x-test for root fixture component tag name (x-test) may not match directory name of root component * chore: remove tag name that is no longer used * refactor(test): destructure! * chore: `fixture-test` feels better than `x-test` to make it more clear it's special * refactor: check scope token for static optimized on/off * test: invalid scope tokens * test: restore `features` feature just removed it yesterday lol * test(engine-server): add scope token validation tests * chore: remove unused file * chore: remove debuggery prop * chore: update to new test config * chore: prettier * chore: rename flag make it more accurate * chore: restore original validation location * chore: clean up duplicate check * test(karma): update test to new behavior
1 parent 340e4a4 commit fa4c927

File tree

23 files changed

+97
-12
lines changed

23 files changed

+97
-12
lines changed

packages/@lwc/engine-core/src/framework/rendering.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import { logError } from '../shared/logger';
2525
import { getComponentTag } from '../shared/format';
2626
import { EmptyArray, shouldBeFormAssociated } from './utils';
2727
import { markComponentAsDirty } from './component';
28-
import { getScopeTokenClass } from './stylesheet';
28+
import { getScopeTokenClass, isValidScopeToken } from './stylesheet';
2929
import { lockDomMutation, patchElementWithRestrictions, unlockDomMutation } from './restrictions';
3030
import {
3131
appendVM,
@@ -605,6 +605,10 @@ function applyStyleScoping(elm: Element, owner: VM, renderer: RendererAPI) {
605605
// Set the class name for `*.scoped.css` style scoping.
606606
const scopeToken = getScopeTokenClass(owner, /* legacy */ false);
607607
if (!isNull(scopeToken)) {
608+
if (!isValidScopeToken(scopeToken)) {
609+
// See W-16614556
610+
throw new Error('stylesheet token must be a valid string');
611+
}
608612
// TODO [#2762]: this dot notation with add is probably problematic
609613
// probably we should have a renderer api for just the add operation
610614
getClassList(elm).add(scopeToken);
@@ -614,6 +618,10 @@ function applyStyleScoping(elm: Element, owner: VM, renderer: RendererAPI) {
614618
if (lwcRuntimeFlags.ENABLE_LEGACY_SCOPE_TOKENS) {
615619
const legacyScopeToken = getScopeTokenClass(owner, /* legacy */ true);
616620
if (!isNull(legacyScopeToken)) {
621+
if (!isValidScopeToken(legacyScopeToken)) {
622+
// See W-16614556
623+
throw new Error('stylesheet token must be a valid string');
624+
}
617625
// TODO [#2762]: this dot notation with add is probably problematic
618626
// probably we should have a renderer api for just the add operation
619627
getClassList(elm).add(legacyScopeToken);

packages/@lwc/engine-core/src/framework/stylesheet.ts

+12
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
ArrayPush,
1010
isArray,
1111
isNull,
12+
isString,
1213
isTrue,
1314
isUndefined,
1415
KEY__NATIVE_ONLY_CSS,
@@ -29,6 +30,8 @@ import type { Template } from './template';
2930
import type { VM } from './vm';
3031
import type { Stylesheet, Stylesheets } from '@lwc/shared';
3132

33+
const VALID_SCOPE_TOKEN_REGEX = /^[a-zA-Z0-9\-_]+$/;
34+
3235
// These are only used for HMR in dev mode
3336
// The "pure" annotations are so that Rollup knows for sure it can remove these from prod mode
3437
let stylesheetsToCssContent: WeakMap<Stylesheet, Set<string>> = /*@__PURE__@*/ new WeakMap();
@@ -394,3 +397,12 @@ export function unrenderStylesheet(stylesheet: Stylesheet) {
394397
cssContentToAbortControllers.delete(cssContent);
395398
}
396399
}
400+
401+
export function isValidScopeToken(token: unknown) {
402+
if (!isString(token)) {
403+
return false;
404+
}
405+
406+
// See W-16614556
407+
return lwcRuntimeFlags.DISABLE_SCOPE_TOKEN_VALIDATION || VALID_SCOPE_TOKEN_REGEX.test(token);
408+
}

packages/@lwc/engine-core/src/framework/template.ts

+7-9
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,12 @@ import api from './api';
2626
import { RenderMode, resetComponentRoot, runWithBoundaryProtection, ShadowMode } from './vm';
2727
import { assertNotProd, EmptyObject } from './utils';
2828
import { defaultEmptyTemplate, isTemplateRegistered } from './secure-template';
29-
import { createStylesheet, getStylesheetsContent, updateStylesheetToken } from './stylesheet';
29+
import {
30+
createStylesheet,
31+
getStylesheetsContent,
32+
isValidScopeToken,
33+
updateStylesheetToken,
34+
} from './stylesheet';
3035
import { logOperationEnd, logOperationStart, OperationId } from './profiler';
3136
import { getTemplateOrSwappedTemplate, setActiveVM } from './hot-swaps';
3237
import { getMapFromClassName } from './modules/computed-class-attr';
@@ -66,14 +71,6 @@ export function setVMBeingRendered(vm: VM | null) {
6671
vmBeingRendered = vm;
6772
}
6873

69-
const VALID_SCOPE_TOKEN_REGEX = /^[a-zA-Z0-9\-_]+$/;
70-
71-
// See W-16614556
72-
// TODO [#2826]: freeze the template object
73-
function isValidScopeToken(token: any) {
74-
return isString(token) && VALID_SCOPE_TOKEN_REGEX.test(token);
75-
}
76-
7774
function validateSlots(vm: VM) {
7875
assertNotProd(); // this method should never leak to prod
7976

@@ -274,6 +271,7 @@ function buildParseFragmentFn(
274271
}
275272

276273
// See W-16614556
274+
// TODO [#2826]: freeze the template object
277275
if (
278276
(hasStyleToken && !isValidScopeToken(stylesheetToken)) ||
279277
(hasLegacyToken && !isValidScopeToken(legacyStylesheetToken))

packages/@lwc/engine-server/src/__tests__/fixtures.spec.ts

+9
Original file line numberDiff line numberDiff line change
@@ -132,8 +132,13 @@ function testFixtures(options?: RollupLwcOptions) {
132132
let result;
133133
let err;
134134
try {
135+
config?.features?.forEach((flag) => {
136+
lwcEngineServer.setFeatureFlagForTest(flag, true);
137+
});
138+
135139
const module: LightningElementConstructor = (await import(compiledFixturePath))
136140
.default;
141+
137142
result = formatHTML(
138143
lwcEngineServer.renderComponent('fixture-test', module, config?.props ?? {})
139144
);
@@ -144,6 +149,10 @@ function testFixtures(options?: RollupLwcOptions) {
144149
err = _err?.message || 'An empty error occurred?!';
145150
}
146151

152+
config?.features?.forEach((flag) => {
153+
lwcEngineServer.setFeatureFlagForTest(flag, false);
154+
});
155+
147156
return {
148157
'expected.html': result,
149158
'error.txt': err,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"entry": "x/component",
3+
"features": ["DISABLE_SCOPE_TOKEN_VALIDATION"]
4+
}

packages/@lwc/engine-server/src/__tests__/fixtures/scope-token-extended/error.txt

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<fixture-test class="stylesheet.token-host" data-lwc-host-scope-token="stylesheet.token-host">
2+
<style class="stylesheet.token" type="text/css">
3+
p.stylesheet.token {font-size: 2em;}
4+
</style>
5+
<p class="stylesheet.token">
6+
je suis une pomme de terre
7+
</p>
8+
</fixture-test>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template lwc:render-mode="light">
2+
<p>je suis une pomme de terre</p>
3+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { LightningElement } from 'lwc';
2+
import cmp from './component.html';
3+
cmp.stylesheetToken = 'stylesheet.token';
4+
5+
export default class HelloWorld extends LightningElement {
6+
static renderMode = 'light';
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
p {
2+
font-size: 2em;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template lwc:render-mode="light">
2+
<p>je suis une pomme de terre</p>
3+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"entry": "x/component"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
stylesheet token must be a valid string

packages/@lwc/engine-server/src/__tests__/fixtures/scope-token/expected.html

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template lwc:render-mode="light">
2+
<p>je suis une pomme de terre</p>
3+
</template>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { LightningElement } from 'lwc';
2+
import cmp from './component.html';
3+
cmp.stylesheetToken = 'stylesheet.token';
4+
5+
export default class HelloWorld extends LightningElement {
6+
static renderMode = 'light';
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
p {
2+
font-size: 2em;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template lwc:render-mode="light">
2+
<p>je suis une pomme de terre</p>
3+
</template>

packages/@lwc/features/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const features: FeatureFlagMap = {
2020
ENABLE_FORCE_SHADOW_MIGRATE_MODE: null,
2121
ENABLE_EXPERIMENTAL_SIGNALS: null,
2222
DISABLE_SYNTHETIC_SHADOW: null,
23+
DISABLE_SCOPE_TOKEN_VALIDATION: null,
2324
LEGACY_LOCKER_ENABLED: null,
2425
};
2526

packages/@lwc/features/src/types.ts

+5
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@ export interface FeatureFlagMap {
7676
*/
7777
DISABLE_SYNTHETIC_SHADOW: FeatureFlagValue;
7878

79+
/**
80+
* If true, the contents of stylesheet scope tokens are not validated.
81+
*/
82+
DISABLE_SCOPE_TOKEN_VALIDATION: FeatureFlagValue;
83+
7984
/**
8085
* If true, then lightning legacy locker is supported, otherwise lightning legacy locker will not function
8186
* properly.

packages/@lwc/integration-karma/test/rendering/sanitize-stylesheet-token/index.spec.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,11 @@ props.forEach((prop) => {
6767

6868
if (
6969
process.env.NATIVE_SHADOW &&
70-
process.env.DISABLE_STATIC_CONTENT_OPTIMIZATION
70+
process.env.DISABLE_STATIC_CONTENT_OPTIMIZATION &&
71+
Ctor !== Scoping
7172
) {
7273
// If we're rendering in native shadow and the static content optimization is disabled,
73-
// then there's no problem with non-string stylesheet tokens because they are only rendered
74+
// then there's no problem with invalid stylesheet tokens because they are only rendered
7475
// as class attribute values using either `classList` or `setAttribute` (and this only applies
7576
// when `*.scoped.css` is being used).
7677
expect(elm.shadowRoot.children.length).toBe(1);

packages/@lwc/ssr-compiler/src/__tests__/utils/expected-failures.ts

+2
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ export const expectedFailures = new Set([
1212
'attribute-global-html/as-component-prop/without-@api/config.json',
1313
'known-boolean-attributes/default-def-html-attributes/static-on-component/config.json',
1414
'wire/errors/throws-when-colliding-prop-then-method/config.json',
15+
'scope-token/config.json',
16+
'scope-token-extended/config.json',
1517
]);

scripts/test-utils/test-fixture-dir.ts

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import path from 'node:path';
1010
import { AssertionError } from 'node:assert';
1111
import { test } from 'vitest';
1212
import * as glob from 'glob';
13+
1314
const { globSync } = glob;
1415

1516
type TestFixtureOutput = { [filename: string]: unknown };

0 commit comments

Comments
 (0)