Skip to content

Commit 5e0aaef

Browse files
authored
[heft-sass] Ensure valid d.ts output for non-identifier classnames (#5233)
Co-authored-by: David Michon <dmichon-msft@users.noreply.github.com>
1 parent e45c51a commit 5e0aaef

File tree

8 files changed

+41
-1
lines changed

8 files changed

+41
-1
lines changed

build-tests/heft-sass-test/src/ExampleApp.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ export class ExampleApp extends React.Component {
3131
<li className={stylesUseAltSyntax.exampleListItem2}>2nd</li>
3232
<li className={stylesUseAltSyntax.exampleListItem3}>3rd</li>
3333
</ul>
34+
<p className={altSyntaxStyles['style-with-dashes']}>This element has a complex class name.</p>
3435
</div>
3536
</div>
3637
);

build-tests/heft-sass-test/src/stylesAltSyntax.module.scss

+4
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ $marginValue: '[theme:normalMargin, default: 20px]';
99
.label {
1010
margin-bottom: $marginValue;
1111
}
12+
13+
.style-with-dashes {
14+
margin-top: $marginValue;
15+
}

build-tests/heft-sass-test/src/test/__snapshots__/lib-commonjs.test.ts.snap

+4
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,10 @@ exports[`SASS CJS Shims stylesAltSyntax.module.scss: stylesAltSyntax.module.css
118118
*/
119119
.label {
120120
margin-bottom: var(--normalMargin, 20px);
121+
}
122+
123+
.style-with-dashes {
124+
margin-top: var(--normalMargin, 20px);
121125
}"
122126
`;
123127

build-tests/heft-sass-test/src/test/__snapshots__/lib-css.test.ts.snap

+4
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,10 @@ exports[`SASS No Shims stylesAltSyntax.module.scss: stylesAltSyntax.module.css 1
108108
*/
109109
.label {
110110
margin-bottom: var(--normalMargin, 20px);
111+
}
112+
113+
.style-with-dashes {
114+
margin-top: var(--normalMargin, 20px);
111115
}"
112116
`;
113117

build-tests/heft-sass-test/src/test/__snapshots__/lib.test.ts.snap

+5
Original file line numberDiff line numberDiff line change
@@ -130,12 +130,17 @@ exports[`SASS ESM Shims stylesAltSyntax.module.scss: stylesAltSyntax.module.css
130130
*/
131131
.label {
132132
margin-bottom: var(--normalMargin, 20px);
133+
}
134+
135+
.style-with-dashes {
136+
margin-top: var(--normalMargin, 20px);
133137
}"
134138
`;
135139

136140
exports[`SASS ESM Shims stylesAltSyntax.module.scss: stylesAltSyntax.module.scss.d.ts 1`] = `
137141
"declare interface IStyles {
138142
label: string;
143+
\\"style-with-dashes\\": string;
139144
}
140145
declare const styles: IStyles;
141146
export default styles;"

build-tests/heft-sass-test/src/test/__snapshots__/sass-ts.test.ts.snap

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ Array [
3737
exports[`SASS Typings stylesAltSyntax.module.scss: stylesAltSyntax.module.scss.d.ts 1`] = `
3838
"declare interface IStyles {
3939
label: string;
40+
\\"style-with-dashes\\": string;
4041
}
4142
declare const styles: IStyles;
4243
export default styles;"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"changes": [
3+
{
4+
"packageName": "@rushstack/heft-sass-plugin",
5+
"comment": "Quote classnames in .d.ts files to handle non-identifier characters.",
6+
"type": "patch"
7+
}
8+
],
9+
"packageName": "@rushstack/heft-sass-plugin"
10+
}

heft-plugins/heft-sass-plugin/src/SassProcessor.ts

+12-1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ import {
3434
Sort
3535
} from '@rushstack/node-core-library';
3636

37+
const SIMPLE_IDENTIFIER_REGEX: RegExp = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/;
38+
3739
/**
3840
* @public
3941
*/
@@ -817,13 +819,22 @@ export class SassProcessor {
817819
if (this._options.exportAsDefault) {
818820
source.push(`declare interface IStyles {`);
819821
for (const className of Object.keys(moduleMap)) {
820-
source.push(` ${className}: string;`);
822+
const safeClassName: string = SIMPLE_IDENTIFIER_REGEX.test(className)
823+
? className
824+
: JSON.stringify(className);
825+
// Quote and escape class names as needed.
826+
source.push(` ${safeClassName}: string;`);
821827
}
822828
source.push(`}`);
823829
source.push(`declare const styles: IStyles;`);
824830
source.push(`export default styles;`);
825831
} else {
826832
for (const className of Object.keys(moduleMap)) {
833+
if (!SIMPLE_IDENTIFIER_REGEX.test(className)) {
834+
throw new Error(
835+
`Class name "${className}" is not a valid identifier and may only be exported using "exportAsDefault: true"`
836+
);
837+
}
827838
source.push(`export const ${className}: string;`);
828839
}
829840
}

0 commit comments

Comments
 (0)