diff --git a/build-tests/heft-sass-test/src/ExampleApp.tsx b/build-tests/heft-sass-test/src/ExampleApp.tsx index 5f9e7d817b..0fca1af044 100644 --- a/build-tests/heft-sass-test/src/ExampleApp.tsx +++ b/build-tests/heft-sass-test/src/ExampleApp.tsx @@ -31,6 +31,7 @@ export class ExampleApp extends React.Component {
  • 2nd
  • 3rd
  • +

    This element has a complex class name.

    ); diff --git a/build-tests/heft-sass-test/src/stylesAltSyntax.module.scss b/build-tests/heft-sass-test/src/stylesAltSyntax.module.scss index a3dc20a38f..c97eebeb3c 100644 --- a/build-tests/heft-sass-test/src/stylesAltSyntax.module.scss +++ b/build-tests/heft-sass-test/src/stylesAltSyntax.module.scss @@ -9,3 +9,7 @@ $marginValue: '[theme:normalMargin, default: 20px]'; .label { margin-bottom: $marginValue; } + +.style-with-dashes { + margin-top: $marginValue; +} diff --git a/build-tests/heft-sass-test/src/test/__snapshots__/lib-commonjs.test.ts.snap b/build-tests/heft-sass-test/src/test/__snapshots__/lib-commonjs.test.ts.snap index 6561952445..76f4a418ba 100644 --- a/build-tests/heft-sass-test/src/test/__snapshots__/lib-commonjs.test.ts.snap +++ b/build-tests/heft-sass-test/src/test/__snapshots__/lib-commonjs.test.ts.snap @@ -118,6 +118,10 @@ exports[`SASS CJS Shims stylesAltSyntax.module.scss: stylesAltSyntax.module.css */ .label { margin-bottom: var(--normalMargin, 20px); +} + +.style-with-dashes { + margin-top: var(--normalMargin, 20px); }" `; diff --git a/build-tests/heft-sass-test/src/test/__snapshots__/lib-css.test.ts.snap b/build-tests/heft-sass-test/src/test/__snapshots__/lib-css.test.ts.snap index 439187a2df..8da1f9cbaf 100644 --- a/build-tests/heft-sass-test/src/test/__snapshots__/lib-css.test.ts.snap +++ b/build-tests/heft-sass-test/src/test/__snapshots__/lib-css.test.ts.snap @@ -108,6 +108,10 @@ exports[`SASS No Shims stylesAltSyntax.module.scss: stylesAltSyntax.module.css 1 */ .label { margin-bottom: var(--normalMargin, 20px); +} + +.style-with-dashes { + margin-top: var(--normalMargin, 20px); }" `; diff --git a/build-tests/heft-sass-test/src/test/__snapshots__/lib.test.ts.snap b/build-tests/heft-sass-test/src/test/__snapshots__/lib.test.ts.snap index 3a5d74f964..d92edd731a 100644 --- a/build-tests/heft-sass-test/src/test/__snapshots__/lib.test.ts.snap +++ b/build-tests/heft-sass-test/src/test/__snapshots__/lib.test.ts.snap @@ -130,12 +130,17 @@ exports[`SASS ESM Shims stylesAltSyntax.module.scss: stylesAltSyntax.module.css */ .label { margin-bottom: var(--normalMargin, 20px); +} + +.style-with-dashes { + margin-top: var(--normalMargin, 20px); }" `; exports[`SASS ESM Shims stylesAltSyntax.module.scss: stylesAltSyntax.module.scss.d.ts 1`] = ` "declare interface IStyles { label: string; + \\"style-with-dashes\\": string; } declare const styles: IStyles; export default styles;" diff --git a/build-tests/heft-sass-test/src/test/__snapshots__/sass-ts.test.ts.snap b/build-tests/heft-sass-test/src/test/__snapshots__/sass-ts.test.ts.snap index 1c7500668b..cfe3f5336b 100644 --- a/build-tests/heft-sass-test/src/test/__snapshots__/sass-ts.test.ts.snap +++ b/build-tests/heft-sass-test/src/test/__snapshots__/sass-ts.test.ts.snap @@ -37,6 +37,7 @@ Array [ exports[`SASS Typings stylesAltSyntax.module.scss: stylesAltSyntax.module.scss.d.ts 1`] = ` "declare interface IStyles { label: string; + \\"style-with-dashes\\": string; } declare const styles: IStyles; export default styles;" diff --git a/common/changes/@rushstack/heft-sass-plugin/sass-quote-classes_2025-05-14-18-43.json b/common/changes/@rushstack/heft-sass-plugin/sass-quote-classes_2025-05-14-18-43.json new file mode 100644 index 0000000000..572596d136 --- /dev/null +++ b/common/changes/@rushstack/heft-sass-plugin/sass-quote-classes_2025-05-14-18-43.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/heft-sass-plugin", + "comment": "Quote classnames in .d.ts files to handle non-identifier characters.", + "type": "patch" + } + ], + "packageName": "@rushstack/heft-sass-plugin" +} \ No newline at end of file diff --git a/heft-plugins/heft-sass-plugin/src/SassProcessor.ts b/heft-plugins/heft-sass-plugin/src/SassProcessor.ts index 76b4db9d83..e202ceb41f 100644 --- a/heft-plugins/heft-sass-plugin/src/SassProcessor.ts +++ b/heft-plugins/heft-sass-plugin/src/SassProcessor.ts @@ -34,6 +34,8 @@ import { Sort } from '@rushstack/node-core-library'; +const SIMPLE_IDENTIFIER_REGEX: RegExp = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/; + /** * @public */ @@ -817,13 +819,22 @@ export class SassProcessor { if (this._options.exportAsDefault) { source.push(`declare interface IStyles {`); for (const className of Object.keys(moduleMap)) { - source.push(` ${className}: string;`); + const safeClassName: string = SIMPLE_IDENTIFIER_REGEX.test(className) + ? className + : JSON.stringify(className); + // Quote and escape class names as needed. + source.push(` ${safeClassName}: string;`); } source.push(`}`); source.push(`declare const styles: IStyles;`); source.push(`export default styles;`); } else { for (const className of Object.keys(moduleMap)) { + if (!SIMPLE_IDENTIFIER_REGEX.test(className)) { + throw new Error( + `Class name "${className}" is not a valid identifier and may only be exported using "exportAsDefault: true"` + ); + } source.push(`export const ${className}: string;`); } }