From 3b568bb95a3b3bbcc7d7342819daa7ef35a4eb0b Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Fri, 10 Apr 2026 16:08:22 -0700 Subject: [PATCH 1/4] [heft-sass-plugin] Improve README and sass.json template - Rewrote README with setup instructions, configuration reference table, CSS modules vs. global stylesheets explanation, shim generation docs, import resolution notes, and plugin accessor API - Updated sass.json template: removed non-existent preserveSCSSExtension and importIncludePaths options, added missing nonModuleFileExtensions and preserveIcssExports, updated cssOutputFolders example to show shimModuleFormat usage Co-Authored-By: Claude Sonnet 4.6 --- ...eft-sass-plugin-docs_2026-04-10-23-08.json | 10 + heft-plugins/heft-sass-plugin/README.md | 242 +++++++++++++++++- .../heft-sass-plugin/src/templates/sass.json | 72 +++--- 3 files changed, 290 insertions(+), 34 deletions(-) create mode 100644 common/changes/@rushstack/heft-sass-plugin/improve-heft-sass-plugin-docs_2026-04-10-23-08.json diff --git a/common/changes/@rushstack/heft-sass-plugin/improve-heft-sass-plugin-docs_2026-04-10-23-08.json b/common/changes/@rushstack/heft-sass-plugin/improve-heft-sass-plugin-docs_2026-04-10-23-08.json new file mode 100644 index 00000000000..e187efa5ee4 --- /dev/null +++ b/common/changes/@rushstack/heft-sass-plugin/improve-heft-sass-plugin-docs_2026-04-10-23-08.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/heft-sass-plugin", + "comment": "Improve project README.", + "type": "patch" + } + ], + "packageName": "@rushstack/heft-sass-plugin" +} \ No newline at end of file diff --git a/heft-plugins/heft-sass-plugin/README.md b/heft-plugins/heft-sass-plugin/README.md index 1d6602caf7e..a55f5b462cd 100644 --- a/heft-plugins/heft-sass-plugin/README.md +++ b/heft-plugins/heft-sass-plugin/README.md @@ -1,12 +1,244 @@ # @rushstack/heft-sass-plugin -This is a Heft plugin for using sass-embedded during the "build" stage. -If `sass-embedded` is not supported on your platform, you can override the dependency via npm alias to use the `sass` package instead. +A [Heft](https://heft.rushstack.io/) plugin that compiles SCSS/Sass files during the build phase. It uses [`sass-embedded`](https://www.npmjs.com/package/sass-embedded) under the hood and produces: + +- **TypeScript type definitions** (`.d.ts`) for CSS modules, giving you typed access to class names and `:export` values +- **Compiled CSS files** (optional) in one or more output folders +- **JavaScript shims** (optional) that re-export the CSS for consumption in CommonJS or ESM environments + +> If `sass-embedded` is not supported on your platform, you can substitute it with the [`sass`](https://www.npmjs.com/package/sass) package using an npm alias. ## Links -- [CHANGELOG.md]( - https://github.com/microsoft/rushstack/blob/main/heft-plugins/heft-sass-plugin/CHANGELOG.md) - Find - out what's new in the latest version +- [CHANGELOG.md](https://github.com/microsoft/rushstack/blob/main/heft-plugins/heft-sass-plugin/CHANGELOG.md) - Find out what's new in the latest version Heft is part of the [Rush Stack](https://rushstack.io/) family of projects. + +--- + +## Setup + +### 1. Add the plugin to your project + +In your project's `package.json`: + +```json +{ + "devDependencies": { + "@rushstack/heft": "...", + "@rushstack/heft-sass-plugin": "..." + } +} +``` + +### 2. Register the plugin in `config/heft.json` + +The `sass` task must run before `typescript` so that the generated `.d.ts` files are available when TypeScript compiles your project. + +```json +{ + "$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft.schema.json", + "phasesByName": { + "build": { + "tasksByName": { + "sass": { + "taskPlugin": { + "pluginPackage": "@rushstack/heft-sass-plugin" + } + }, + + "typescript": { + "taskDependencies": ["sass"], + "taskPlugin": { + "pluginPackage": "@rushstack/heft-typescript-plugin" + } + } + } + } + } +} +``` + +### 3. Create `config/sass.json` + +A minimal config uses all defaults: + +```json +{ + "$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft-sass-plugin.schema.json" +} +``` + +A more complete setup that emits CSS and shims for both ESM and CommonJS: + +```json +{ + "$schema": "https://developer.microsoft.com/json-schemas/heft/v0/heft-sass-plugin.schema.json", + "cssOutputFolders": [ + { "folder": "lib-esm", "shimModuleFormat": "esnext" }, + { "folder": "lib-commonjs", "shimModuleFormat": "commonjs" } + ], + "fileExtensions": [".module.scss", ".module.sass"], + "nonModuleFileExtensions": [".global.scss", ".global.sass"], + "silenceDeprecations": ["mixed-decls", "import", "global-builtin", "color-functions"] +} +``` + +### 4. Add generated files to `tsconfig.json` + +Point TypeScript at the generated type definitions by including the `generatedTsFolder` in your `tsconfig.json`: + +```json +{ + "compilerOptions": { + "paths": {} + }, + "include": ["src", "temp/sass-ts"] +} +``` + +## CSS Modules vs. global stylesheets + +The plugin distinguishes between two kinds of files based on their extension: + +**CSS modules** (extensions listed in `fileExtensions`, default: `.sass`, `.scss`, `.css`): +- Processed with [`postcss-modules`](https://www.npmjs.com/package/postcss-modules) +- Class names and `:export` values become properties in a generated TypeScript interface +- The generated `.d.ts` exports a typed `styles` object as its default export + +**Global stylesheets** (extensions listed in `nonModuleFileExtensions`, default: `.global.sass`, `.global.scss`, `.global.css`): +- Compiled to plain CSS with no module scoping +- The generated `.d.ts` is a side-effect-only module (`export {}`) +- Useful for resets, themes, and base styles + +**Partials** (filenames starting with `_`): +- Never compiled to output files; they are only meant to be `@use`d or `@forward`ed by other files + +### Example: CSS module + +```scss +// src/Button.module.scss +.root { + background: blue; +} +.label { + font-size: 14px; +} +:export { + brandColor: #0078d4; +} +``` + +Generated `temp/sass-ts/Button.module.scss.d.ts`: + +```typescript +interface IStyles { + root: string; + label: string; + brandColor: string; +} +declare const styles: IStyles; +export default styles; +``` + +In your TypeScript source: + +```typescript +import styles from './Button.module.scss'; +// styles.root, styles.label, styles.brandColor are all typed strings +``` + +## Configuration reference + +All options are set in `config/sass.json`. Every option is optional. + +| Option | Default | Description | +|---|---|---| +| `srcFolder` | `"src/"` | Root directory that is scanned for SCSS files | +| `generatedTsFolder` | `"temp/sass-ts/"` | Output directory for generated `.d.ts` files | +| `secondaryGeneratedTsFolders` | `[]` | Additional directories to also write `.d.ts` files to (e.g. `"lib-esm"` when publishing typings alongside compiled output) | +| `exportAsDefault` | `true` | When `true`, wraps exports in a typed default interface. When `false`, generates individual named exports (`export const className: string`). Note: `false` is incompatible with `cssOutputFolders`. | +| `cssOutputFolders` | _(none)_ | Folders where compiled `.css` files are written. Each entry is either a plain folder path string, or an object with `folder` and optional `shimModuleFormat` (see below). | +| `fileExtensions` | `[".sass", ".scss", ".css"]` | File extensions to treat as CSS modules | +| `nonModuleFileExtensions` | `[".global.sass", ".global.scss", ".global.css"]` | File extensions to treat as global (non-module) stylesheets | +| `excludeFiles` | `[]` | Paths relative to `srcFolder` to skip entirely | +| `doNotTrimOriginalFileExtension` | `false` | When `true`, preserves the original extension in the CSS output filename. E.g. `styles.scss` → `styles.scss.css` instead of `styles.css`. Useful when downstream tooling needs to distinguish the source format. | +| `preserveIcssExports` | `false` | When `true`, keeps the `:export { }` block in the emitted CSS. This is needed when a webpack loader (e.g. `css-loader`'s `icssParser`) must extract `:export` values at bundle time. Has no effect on the generated `.d.ts`. | +| `silenceDeprecations` | `[]` | List of Sass deprecation codes to suppress (e.g. `"mixed-decls"`, `"import"`, `"global-builtin"`, `"color-functions"`) | +| `ignoreDeprecationsInDependencies` | `false` | Suppresses deprecation warnings that originate from `node_modules` dependencies | +| `extends` | _(none)_ | Path to another `sass.json` config file to inherit settings from | + +### CSS output folders and JS shims + +Each entry in `cssOutputFolders` can be a plain string (folder path only) or an object: + +```json +{ + "folder": "lib-esm", + "shimModuleFormat": "esnext" +} +``` + +When `shimModuleFormat` is set, the plugin writes a `.js` shim alongside each `.css` file. For a CSS module, the shim re-exports the CSS: + +```js +// ESM shim (shimModuleFormat: "esnext") +export { default } from "./Button.module.css"; + +// CommonJS shim (shimModuleFormat: "commonjs") +module.exports = require("./Button.module.css"); +module.exports.default = module.exports; +``` + +For a global stylesheet, the shim is a side-effect-only import: + +```js +// ESM shim +import "./global.global.css"; +export {}; + +// CommonJS shim +require("./global.global.css"); +``` + +## Sass import resolution + +The plugin supports the modern `pkg:` protocol for importing from npm packages: + +```scss +@use "pkg:@fluentui/react/dist/sass/variables"; +``` + +The legacy `~` prefix is automatically converted to `pkg:` for compatibility with older stylesheets: + +```scss +// These are equivalent: +@use "~@fluentui/react/dist/sass/variables"; +@use "pkg:@fluentui/react/dist/sass/variables"; +``` + +## Incremental builds + +The plugin tracks inter-file dependencies (via `@use`, `@forward`, and `@import`) and only recompiles files that changed or whose dependencies changed. This makes `heft build --watch` fast even in large projects. + +## Plugin accessor API + +Other Heft plugins can hook into the Sass compilation pipeline via the `ISassPluginAccessor` interface: + +```typescript +import { ISassPluginAccessor } from '@rushstack/heft-sass-plugin'; + +// In your plugin's apply() method: +const sassAccessor = session.requestAccessToPlugin( + '@rushstack/heft-sass-plugin', + 'sass-plugin', + '@rushstack/heft-sass-plugin' +); + +sassAccessor.hooks.postProcessCss.tapPromise('my-plugin', async (css, filePath) => { + // Transform CSS after Sass compilation but before it is written to cssOutputFolders + return transformedCss; +}); +``` + +The `postProcessCss` hook is an `AsyncSeriesWaterfallHook` that passes the compiled CSS string and source file path through each tap in sequence. diff --git a/heft-plugins/heft-sass-plugin/src/templates/sass.json b/heft-plugins/heft-sass-plugin/src/templates/sass.json index a7218c0eb10..259d966519a 100644 --- a/heft-plugins/heft-sass-plugin/src/templates/sass.json +++ b/heft-plugins/heft-sass-plugin/src/templates/sass.json @@ -10,7 +10,7 @@ * * To delete an inherited setting, set it to `null` in this file. */ - // "extends": "base-project/config/serve-command.json", + // "extends": "base-project/config/sass.json", /** * The root directory for project source code. @@ -27,74 +27,88 @@ // "generatedTsFolder": "temp/sass-ts/", /** - * Optional additional folders to which Sass typings should be output. + * Optional additional folders to which Sass typings should be output. Useful when publishing typings + * alongside compiled output (e.g. "lib-esm"). */ // "secondaryGeneratedTsFolders": [], /** - * Determines whether export values are wrapped in a default property, or not. + * Determines whether CSS module exports are wrapped in a typed default interface (true) or emitted as + * individual named exports (false). + * + * Note: setting this to false is incompatible with cssOutputFolders. * * Default value: true */ // "exportAsDefault": false, /** - * If specified, folders where compiled CSS files will be emitted to. They will be named by appending - * ".css" to the source file name for ease of reference translation, unless "preserveSCSSExtension" is set. + * If specified, folders where compiled CSS files will be emitted. Each entry is either a folder path string, + * or an object with a "folder" property and an optional "shimModuleFormat" property. When "shimModuleFormat" + * is set to "commonjs" or "esnext", a JavaScript shim file is emitted alongside each CSS file to re-export it + * in the specified module format. * * Default value: undefined */ - // "cssOutputFolders": [], - - /** - * If set, when emitting compiled CSS from a file with a ".scss" extension, the emitted CSS will have - * the extension ".scss" instead of ".scss.css". - * - * Default value: false - */ - // "preserveSCSSExtension": true, + // "cssOutputFolders": [ + // { "folder": "lib-esm", "shimModuleFormat": "esnext" }, + // { "folder": "lib-commonjs", "shimModuleFormat": "commonjs" } + // ], /** - * Files with these extensions will pass through the Sass transpiler for typings generation. + * Files with these extensions will be treated as CSS modules and pass through the Sass transpiler for + * typings generation and/or CSS emit. * * Default value: [".sass", ".scss", ".css"] */ - // "fileExtensions": [".sass", ".scss"], + // "fileExtensions": [".module.scss", ".module.sass"], /** - * A list of paths used when resolving Sass imports. The paths should be relative to the project root. + * Files with these extensions will be treated as non-module (global) stylesheets and pass through the Sass + * transpiler for typings generation and/or CSS emit. The generated typings are side-effect-only (export {}). * - * Default value: ["node_modules", "src"] + * Default value: [".global.sass", ".global.scss", ".global.css"] */ - // "importIncludePaths": ["node_modules", "src"], + // "nonModuleFileExtensions": [".global.scss", ".global.sass"], /** - * A list of file paths relative to the "src" folder that should be excluded from typings generation. + * A list of file paths relative to the "src" folder that should be excluded from typings generation + * and/or CSS emit. * * Default value: undefined */ // "excludeFiles": [], /** - * If set, deprecation warnings from dependencies will be suppressed. + * If true, the original file extension will not be trimmed when generating the output CSS filename. + * For example, "styles.scss" will generate "styles.scss.css" instead of "styles.css". * * Default value: false */ - // "ignoreDeprecationsInDependencies": true, + // "doNotTrimOriginalFileExtension": true, /** - * If set, the specified deprecation warnings will be suppressed. + * If true, the ICSS ":export" block will be preserved in the emitted CSS output. This is necessary when + * the CSS is consumed by a webpack loader (e.g. css-loader's icssParser) that extracts ":export" values + * at bundle time. Has no effect on the generated ".d.ts" file. * - * Default value: [] + * Default value: false */ - // "silenceDeprecations": ["mixed-decls"], + // "preserveIcssExports": true, /** - * If true, the original file extension will not be trimmed when generating the output CSS file. - * The generated CSS file will retain its original extension. For example, "styles.scss" will generate - * "styles.scss.css" instead of "styles.css". + * If set, deprecation warnings that originate from dependencies will be suppressed. * * Default value: false */ - // "doNotTrimOriginalFileExtension": true + // "ignoreDeprecationsInDependencies": true, + + /** + * A list of Sass deprecation codes to silence. Useful for suppressing known warnings from deprecated + * features that are not yet actionable. Common values: "mixed-decls", "import", "global-builtin", + * "color-functions". + * + * Default value: [] + */ + // "silenceDeprecations": ["mixed-decls"] } From 81bda2774a6e648feceb79e3fd6e969989ddad1b Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Fri, 10 Apr 2026 16:34:20 -0700 Subject: [PATCH 2/4] fixup! [heft-sass-plugin] Improve README and sass.json template --- apps/rush-mcp-server/README.md | 4 ++-- ...ove-heft-sass-plugin-docs_2026-04-10-23-34.json | 10 ++++++++++ ...ove-heft-sass-plugin-docs_2026-04-10-23-34.json | 10 ++++++++++ ...ove-heft-sass-plugin-docs_2026-04-10-23-34.json | 10 ++++++++++ ...ove-heft-sass-plugin-docs_2026-04-10-23-34.json | 10 ++++++++++ ...ove-heft-sass-plugin-docs_2026-04-10-23-34.json | 10 ++++++++++ ...ove-heft-sass-plugin-docs_2026-04-10-23-34.json | 10 ++++++++++ eslint/eslint-plugin/README.md | 6 +++--- .../config/jestMocks/prettier.js | 2 +- .../src/TsDocReleaseTagHelpers.ts | 2 +- .../src/test/SassProcessor.test.ts | 6 +++--- .../src/test/fixtures/export-only.module.scss | 2 +- .../src/test/fixtures/global-styles.global.scss | 2 +- .../src/test/fixtures/invalid.module.scss | 2 +- .../heft-static-asset-typings-plugin/README.md | 12 ++++++------ libraries/heft-config-file/README.md | 4 ++-- .../rush-lib/src/api/RushProjectConfiguration.ts | 14 +++++++------- .../logic/incremental/test/InputsSnapshot.test.ts | 10 +++++----- .../operations/test/AsyncOperationQueue.test.ts | 4 ++-- .../README.md | 8 ++++---- 20 files changed, 99 insertions(+), 39 deletions(-) create mode 100644 common/changes/@microsoft/rush/improve-heft-sass-plugin-docs_2026-04-10-23-34.json create mode 100644 common/changes/@rushstack/eslint-plugin/improve-heft-sass-plugin-docs_2026-04-10-23-34.json create mode 100644 common/changes/@rushstack/heft-config-file/improve-heft-sass-plugin-docs_2026-04-10-23-34.json create mode 100644 common/changes/@rushstack/heft-json-schema-typings-plugin/improve-heft-sass-plugin-docs_2026-04-10-23-34.json create mode 100644 common/changes/@rushstack/heft-static-asset-typings-plugin/improve-heft-sass-plugin-docs_2026-04-10-23-34.json create mode 100644 common/changes/@rushstack/mcp-server/improve-heft-sass-plugin-docs_2026-04-10-23-34.json diff --git a/apps/rush-mcp-server/README.md b/apps/rush-mcp-server/README.md index fc0f6f81523..3f60e2eca73 100644 --- a/apps/rush-mcp-server/README.md +++ b/apps/rush-mcp-server/README.md @@ -1,6 +1,6 @@ # @rushstack/mcp-server -With the rapid advancement of LLMs, AI applications like Trae, Cursor, Cline, Windsurf, and others have been thriving. However, due to the large scale of monorepos and the context limitations of LLMs, it’s difficult for these models to fully understand your monorepo. This is where @rushstack/mcp-server comes in — by providing a suite of MCP tools, it enables LLMs to better comprehend your monorepo and assist you more effectively with daily development tasks in a Rush-based monorepo environment. +With the rapid advancement of LLMs, AI applications like Trae, Cursor, Cline, Windsurf, and others have been thriving. However, due to the large scale of monorepos and the context limitations of LLMs, it’s difficult for these models to fully understand your monorepo. This is where @rushstack/mcp-server comes in - by providing a suite of MCP tools, it enables LLMs to better comprehend your monorepo and assist you more effectively with daily development tasks in a Rush-based monorepo environment. ## Usage @@ -19,7 +19,7 @@ With the rapid advancement of LLMs, AI applications like Trae, Cursor, Cline, Wi } ``` -3. Congratulations 🎉 You’ve completed the setup — Rush MCP is now ready to use! +3. Congratulations 🎉 You’ve completed the setup - Rush MCP is now ready to use! ## Available Tools diff --git a/common/changes/@microsoft/rush/improve-heft-sass-plugin-docs_2026-04-10-23-34.json b/common/changes/@microsoft/rush/improve-heft-sass-plugin-docs_2026-04-10-23-34.json new file mode 100644 index 00000000000..bd7ff97cb34 --- /dev/null +++ b/common/changes/@microsoft/rush/improve-heft-sass-plugin-docs_2026-04-10-23-34.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@microsoft/rush", + "comment": "", + "type": "none" + } + ], + "packageName": "@microsoft/rush" +} \ No newline at end of file diff --git a/common/changes/@rushstack/eslint-plugin/improve-heft-sass-plugin-docs_2026-04-10-23-34.json b/common/changes/@rushstack/eslint-plugin/improve-heft-sass-plugin-docs_2026-04-10-23-34.json new file mode 100644 index 00000000000..dcf93469653 --- /dev/null +++ b/common/changes/@rushstack/eslint-plugin/improve-heft-sass-plugin-docs_2026-04-10-23-34.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/eslint-plugin", + "comment": "", + "type": "none" + } + ], + "packageName": "@rushstack/eslint-plugin" +} \ No newline at end of file diff --git a/common/changes/@rushstack/heft-config-file/improve-heft-sass-plugin-docs_2026-04-10-23-34.json b/common/changes/@rushstack/heft-config-file/improve-heft-sass-plugin-docs_2026-04-10-23-34.json new file mode 100644 index 00000000000..9b24a4fdcca --- /dev/null +++ b/common/changes/@rushstack/heft-config-file/improve-heft-sass-plugin-docs_2026-04-10-23-34.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/heft-config-file", + "comment": "", + "type": "none" + } + ], + "packageName": "@rushstack/heft-config-file" +} \ No newline at end of file diff --git a/common/changes/@rushstack/heft-json-schema-typings-plugin/improve-heft-sass-plugin-docs_2026-04-10-23-34.json b/common/changes/@rushstack/heft-json-schema-typings-plugin/improve-heft-sass-plugin-docs_2026-04-10-23-34.json new file mode 100644 index 00000000000..2f43b097961 --- /dev/null +++ b/common/changes/@rushstack/heft-json-schema-typings-plugin/improve-heft-sass-plugin-docs_2026-04-10-23-34.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/heft-json-schema-typings-plugin", + "comment": "", + "type": "none" + } + ], + "packageName": "@rushstack/heft-json-schema-typings-plugin" +} \ No newline at end of file diff --git a/common/changes/@rushstack/heft-static-asset-typings-plugin/improve-heft-sass-plugin-docs_2026-04-10-23-34.json b/common/changes/@rushstack/heft-static-asset-typings-plugin/improve-heft-sass-plugin-docs_2026-04-10-23-34.json new file mode 100644 index 00000000000..6e62e958d5d --- /dev/null +++ b/common/changes/@rushstack/heft-static-asset-typings-plugin/improve-heft-sass-plugin-docs_2026-04-10-23-34.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/heft-static-asset-typings-plugin", + "comment": "", + "type": "none" + } + ], + "packageName": "@rushstack/heft-static-asset-typings-plugin" +} \ No newline at end of file diff --git a/common/changes/@rushstack/mcp-server/improve-heft-sass-plugin-docs_2026-04-10-23-34.json b/common/changes/@rushstack/mcp-server/improve-heft-sass-plugin-docs_2026-04-10-23-34.json new file mode 100644 index 00000000000..827fb92cfee --- /dev/null +++ b/common/changes/@rushstack/mcp-server/improve-heft-sass-plugin-docs_2026-04-10-23-34.json @@ -0,0 +1,10 @@ +{ + "changes": [ + { + "packageName": "@rushstack/mcp-server", + "comment": "", + "type": "none" + } + ], + "packageName": "@rushstack/mcp-server" +} \ No newline at end of file diff --git a/eslint/eslint-plugin/README.md b/eslint/eslint-plugin/README.md index 037045492d7..b76d3142ea2 100644 --- a/eslint/eslint-plugin/README.md +++ b/eslint/eslint-plugin/README.md @@ -142,7 +142,7 @@ import( #### Notes - If your bundler does not understand Webpack magic comments (e.g. plain Node ESM loader), disable this rule for that project. -- Choose stable, descriptive chunk names—avoid including hashes, timestamps, or environment‑specific tokens. +- Choose stable, descriptive chunk names-avoid including hashes, timestamps, or environment‑specific tokens. - Chunk names share a global namespace in the final bundle; avoid collisions to keep analysis clear. #### Rationale @@ -531,14 +531,14 @@ unmountComponentAtNode(b); ```ts // No legacy ReactDOM render/unmount usage in this file -// (e.g. uses React 18 createRoot API or just defines components) — rule passes +// (e.g. uses React 18 createRoot API or just defines components) - rule passes ``` #### Notes - The rule does not attempt dataflow analysis to verify the same container node is passed; it only enforces count parity. - Modern React apps using `createRoot()` should migrate to pairing `root.unmount()`. This legacy rule helps older code until migration is complete. -- Multiple files can coordinate unmounting (e.g. via a shared cleanup utility); in that case this rule will flag the imbalance—consider colocating the unmount or disabling the rule for that file. +- Multiple files can coordinate unmounting (e.g. via a shared cleanup utility); in that case this rule will flag the imbalance-consider colocating the unmount or disabling the rule for that file. #### Rationale diff --git a/heft-plugins/heft-json-schema-typings-plugin/config/jestMocks/prettier.js b/heft-plugins/heft-json-schema-typings-plugin/config/jestMocks/prettier.js index c5d39cf2978..1fb34a72b8c 100644 --- a/heft-plugins/heft-json-schema-typings-plugin/config/jestMocks/prettier.js +++ b/heft-plugins/heft-json-schema-typings-plugin/config/jestMocks/prettier.js @@ -1,6 +1,6 @@ // Stub for prettier. json-schema-to-typescript eagerly require('prettier') at // module load time. Prettier v3's CJS entry does a top-level dynamic import() // which crashes inside Jest's VM sandbox on Node 22+. Since compile() is called -// with format: false, prettier is never invoked — this stub just prevents the +// with format: false, prettier is never invoked - this stub just prevents the // module-load crash. module.exports = {}; diff --git a/heft-plugins/heft-json-schema-typings-plugin/src/TsDocReleaseTagHelpers.ts b/heft-plugins/heft-json-schema-typings-plugin/src/TsDocReleaseTagHelpers.ts index 3c736a094e8..3f8b1d8af48 100644 --- a/heft-plugins/heft-json-schema-typings-plugin/src/TsDocReleaseTagHelpers.ts +++ b/heft-plugins/heft-json-schema-typings-plugin/src/TsDocReleaseTagHelpers.ts @@ -5,7 +5,7 @@ export const X_TSDOC_RELEASE_TAG_KEY: 'x-tsdoc-release-tag' = 'x-tsdoc-release-t const RELEASE_TAG_PATTERN: RegExp = /^@[a-z]+$/; /** - * Validates that a string looks like a TSDoc release tag — a single lowercase + * Validates that a string looks like a TSDoc release tag - a single lowercase * word starting with `@` (e.g. `@public`, `@beta`, `@internal`). */ export function _validateTsDocReleaseTag(value: string, schemaPath: string): void { diff --git a/heft-plugins/heft-sass-plugin/src/test/SassProcessor.test.ts b/heft-plugins/heft-sass-plugin/src/test/SassProcessor.test.ts index 6f9ca801125..72d5abf42f9 100644 --- a/heft-plugins/heft-sass-plugin/src/test/SassProcessor.test.ts +++ b/heft-plugins/heft-sass-plugin/src/test/SassProcessor.test.ts @@ -13,7 +13,7 @@ import { type ICssOutputFolder, type ISassProcessorOptions, SassProcessor } from const projectFolder: string = path.resolve(__dirname, '../..'); const fixturesFolder: string = path.resolve(__dirname, '../../src/test/fixtures'); -// Fake output folder paths — never actually written to disk because FileSystem.writeFileAsync is mocked. +// Fake output folder paths - never actually written to disk because FileSystem.writeFileAsync is mocked. const FAKE_OUTPUT_BASE_FOLDER: string = '/fake/output'; const NORMALIZED_PLATFORM_FAKE_OUTPUT_BASE_FOLDER: string = Path.convertToSlashes( nodeJsPath.resolve(FAKE_OUTPUT_BASE_FOLDER) @@ -238,7 +238,7 @@ describe(SassProcessor.name, () => { const { processor } = createProcessor(terminalProvider); await compileFixtureAsync(processor, 'mixin-with-exports.module.scss'); const css: string = getCssOutput('mixin-with-exports.module.scss'); - // Mixin output should be inlined — no @mixin or @include in the output + // Mixin output should be inlined - no @mixin or @include in the output expect(css).not.toContain('@mixin'); expect(css).not.toContain('@include'); expect(css).toContain('display: flex'); @@ -347,7 +347,7 @@ describe(SassProcessor.name, () => { cssOutputFolders: [{ folder: CSS_OUTPUT_FOLDER, shimModuleFormat: undefined }] }); await compileFixtureAsync(processor, 'classes-and-exports.module.scss'); - // Only the CSS and DTS files should be written — no .js shim + // Only the CSS and DTS files should be written - no .js shim const shimPaths: string[] = getAllWrittenPathsMatching('.module.scss.js'); expect(shimPaths).toHaveLength(0); }); diff --git a/heft-plugins/heft-sass-plugin/src/test/fixtures/export-only.module.scss b/heft-plugins/heft-sass-plugin/src/test/fixtures/export-only.module.scss index abe60ef5051..9b6278b7406 100644 --- a/heft-plugins/heft-sass-plugin/src/test/fixtures/export-only.module.scss +++ b/heft-plugins/heft-sass-plugin/src/test/fixtures/export-only.module.scss @@ -1,4 +1,4 @@ -// A CSS module that only exports ICSS values — no class names. +// A CSS module that only exports ICSS values - no class names. // Used to verify that the :export block is preserved or stripped based on preserveIcssExports. :export { primaryColor: #0078d4; diff --git a/heft-plugins/heft-sass-plugin/src/test/fixtures/global-styles.global.scss b/heft-plugins/heft-sass-plugin/src/test/fixtures/global-styles.global.scss index bd77ea5d649..a03043e40b4 100644 --- a/heft-plugins/heft-sass-plugin/src/test/fixtures/global-styles.global.scss +++ b/heft-plugins/heft-sass-plugin/src/test/fixtures/global-styles.global.scss @@ -1,4 +1,4 @@ -// Non-module global stylesheet — processed as plain CSS, not a CSS module. +// Non-module global stylesheet - processed as plain CSS, not a CSS module. // Used to verify that global files produce no class exports and generate correct shims. $body-font: 'Segoe UI', sans-serif; $heading-color: #333; diff --git a/heft-plugins/heft-sass-plugin/src/test/fixtures/invalid.module.scss b/heft-plugins/heft-sass-plugin/src/test/fixtures/invalid.module.scss index 7c9df51305b..b91bcd341fd 100644 --- a/heft-plugins/heft-sass-plugin/src/test/fixtures/invalid.module.scss +++ b/heft-plugins/heft-sass-plugin/src/test/fixtures/invalid.module.scss @@ -1,4 +1,4 @@ -// Intentionally invalid SCSS — used to verify that SassProcessor emits errors correctly. +// Intentionally invalid SCSS - used to verify that SassProcessor emits errors correctly. .broken { color:; } diff --git a/heft-plugins/heft-static-asset-typings-plugin/README.md b/heft-plugins/heft-static-asset-typings-plugin/README.md index e73795a4223..6fbaf7bf110 100644 --- a/heft-plugins/heft-static-asset-typings-plugin/README.md +++ b/heft-plugins/heft-static-asset-typings-plugin/README.md @@ -3,12 +3,12 @@ This Heft plugin generates TypeScript `.d.ts` typings for static asset files, enabling type-safe `import` statements for non-TypeScript files. It provides two task plugins: -- **`resource-assets-plugin`** — Generates `.d.ts` typings for _resource_ files such as images (`.png`, +- **`resource-assets-plugin`** - Generates `.d.ts` typings for _resource_ files such as images (`.png`, `.jpg`, `.svg`, etc.) and fonts. These are opaque binary blobs whose content is not meaningful to JavaScript; the generated typing simply exports a default `string` representing the asset URL (e.g. as resolved by a bundler's asset loader). -- **`source-assets-plugin`** — Generates `.d.ts` typings _and_ JavaScript module output for _source_ +- **`source-assets-plugin`** - Generates `.d.ts` typings _and_ JavaScript module output for _source_ files (`.html`, `.css`, `.txt`, `.md`, etc.) whose textual content is consumed at runtime. The generated JS modules read the file and re-export its content as a default `string`, making these assets importable as ES modules. @@ -31,7 +31,7 @@ Both plugins support incremental and watch-mode builds. ### Resource assets (images, fonts, etc.) - **Inline configuration** — specify options directly in heft.json: + **Inline configuration** - specify options directly in heft.json: ```jsonc { @@ -62,7 +62,7 @@ Both plugins support incremental and watch-mode builds. } ``` - **File configuration** — load settings from a riggable config file: + **File configuration** - load settings from a riggable config file: ```jsonc { @@ -196,7 +196,7 @@ Provide configuration directly in heft.json under `options.config`: | Option | Type | Default | Description | | ------------------- | ---------- | ------------------------ | ----------------------------------------------- | -| `fileExtensions` | `string[]` | — | **(required)** File extensions to generate typings for. | +| `fileExtensions` | `string[]` | - | **(required)** File extensions to generate typings for. | | `generatedTsFolders`| `string[]` | `["temp/static-asset-ts"]` | Folders where generated `.d.ts` files are written. The first entry should be listed in `rootDirs` so TypeScript can resolve the asset imports during type-checking. Additional entries are typically your project's published typings folder(s). | | `sourceFolderPath` | `string` | `"src"` | Source folder to scan for asset files. | @@ -206,7 +206,7 @@ Includes all the above, plus: | Option | Type | Default | Description | | ------------------- | ---------- | ------------------------ | ---------------------------------------------------- | -| `cjsOutputFolders` | `string[]` | — | **(required)** Output folders for generated CommonJS `.js` modules. | +| `cjsOutputFolders` | `string[]` | - | **(required)** Output folders for generated CommonJS `.js` modules. | | `esmOutputFolders` | `string[]` | `[]` | Output folders for generated ESM `.js` modules. | ### File mode (`configType: "file"`) diff --git a/libraries/heft-config-file/README.md b/libraries/heft-config-file/README.md index b6a887bd863..c51a808d73a 100644 --- a/libraries/heft-config-file/README.md +++ b/libraries/heft-config-file/README.md @@ -87,7 +87,7 @@ config file using the `"$.inheritanceType"` annotation: } ``` -These annotations work at any nesting level — you can annotate a nested property the same way: +These annotations work at any nesting level - you can annotate a nested property the same way: ```json { @@ -320,7 +320,7 @@ const loader = new ProjectConfigurationFile({ ``` The function receives `(childValue, parentValue)` and must return the merged result. It is not called if the -child sets the property to `null` — in that case the property is simply deleted. +child sets the property to `null` - in that case the property is simply deleted. ### Inheritance precedence diff --git a/libraries/rush-lib/src/api/RushProjectConfiguration.ts b/libraries/rush-lib/src/api/RushProjectConfiguration.ts index 9a533984fa1..ab606aa1c15 100644 --- a/libraries/rush-lib/src/api/RushProjectConfiguration.ts +++ b/libraries/rush-lib/src/api/RushProjectConfiguration.ts @@ -75,9 +75,9 @@ export interface IRushPhaseSharding { /** * The granularity at which the Node.js version is included in the build cache hash. * - * - `"major"` — includes only the major version (e.g. `18`) - * - `"minor"` — includes the major and minor version (e.g. `18.17`) - * - `"patch"` — includes the full version (e.g. `18.17.1`) + * - `"major"` - includes only the major version (e.g. `18`) + * - `"minor"` - includes the major and minor version (e.g. `18.17`) + * - `"patch"` - includes the full version (e.g. `18.17.1`) * * @alpha */ @@ -130,10 +130,10 @@ export interface IOperationSettings { * projects that produce Node.js-version-specific outputs, such as native module builds. * * Allowed values: - * - `true` — alias for `"patch"`, includes the full version (e.g. `18.17.1`) - * - `"major"` — includes only the major version (e.g. `18`) - * - `"minor"` — includes the major and minor version (e.g. `18.17`) - * - `"patch"` — includes the full version (e.g. `18.17.1`) + * - `true` - alias for `"patch"`, includes the full version (e.g. `18.17.1`) + * - `"major"` - includes only the major version (e.g. `18`) + * - `"minor"` - includes the major and minor version (e.g. `18.17`) + * - `"patch"` - includes the full version (e.g. `18.17.1`) */ dependsOnNodeVersion?: boolean | NodeVersionGranularity; diff --git a/libraries/rush-lib/src/logic/incremental/test/InputsSnapshot.test.ts b/libraries/rush-lib/src/logic/incremental/test/InputsSnapshot.test.ts index 9b18c99efaf..4f7e487fd1c 100644 --- a/libraries/rush-lib/src/logic/incremental/test/InputsSnapshot.test.ts +++ b/libraries/rush-lib/src/logic/incremental/test/InputsSnapshot.test.ts @@ -482,7 +482,7 @@ describe(InputsSnapshot.name, () => { ]) }; - // Same major, different minor — should produce the same hash + // Same major, different minor - should produce the same hash const input1: InputsSnapshot = new InputsSnapshot({ ...options, projectMap: new Map([[project, { projectConfig: projectConfig as RushProjectConfiguration }]]), @@ -500,7 +500,7 @@ describe(InputsSnapshot.name, () => { expect(result1).toEqual(result2); - // Different major — should produce a different hash + // Different major - should produce a different hash const input3: InputsSnapshot = new InputsSnapshot({ ...options, projectMap: new Map([[project, { projectConfig: projectConfig as RushProjectConfiguration }]]), @@ -527,7 +527,7 @@ describe(InputsSnapshot.name, () => { ]) }; - // Same major.minor, different patch — should produce the same hash + // Same major.minor, different patch - should produce the same hash const input1: InputsSnapshot = new InputsSnapshot({ ...options, projectMap: new Map([[project, { projectConfig: projectConfig as RushProjectConfiguration }]]), @@ -545,7 +545,7 @@ describe(InputsSnapshot.name, () => { expect(result1).toEqual(result2); - // Different minor — should produce a different hash + // Different minor - should produce a different hash const input3: InputsSnapshot = new InputsSnapshot({ ...options, projectMap: new Map([[project, { projectConfig: projectConfig as RushProjectConfiguration }]]), @@ -602,7 +602,7 @@ describe(InputsSnapshot.name, () => { expect(resultPatch).toEqual(resultTrue); - // Different patch — should produce a different hash + // Different patch - should produce a different hash const input2: InputsSnapshot = new InputsSnapshot({ ...options, projectMap: new Map([[project, { projectConfig: projectConfig as RushProjectConfiguration }]]), diff --git a/libraries/rush-lib/src/logic/operations/test/AsyncOperationQueue.test.ts b/libraries/rush-lib/src/logic/operations/test/AsyncOperationQueue.test.ts index 1fcd1bb4cc7..fd599645463 100644 --- a/libraries/rush-lib/src/logic/operations/test/AsyncOperationQueue.test.ts +++ b/libraries/rush-lib/src/logic/operations/test/AsyncOperationQueue.test.ts @@ -187,7 +187,7 @@ describe(AsyncOperationQueue.name, () => { // Simulate cobuild retry: operation returns to Ready firstAssigned.status = OperationStatus.Ready; - // Assign all three — untried operations should come before the retry + // Assign all three - untried operations should come before the retry const results: OperationExecutionRecord[] = []; for await (const item of queue) { results.push(item); @@ -222,7 +222,7 @@ describe(AsyncOperationQueue.name, () => { opC.status = OperationStatus.Success; queue.complete(opC); - // B is freshly unblocked (never assigned), A is a cobuild retry — B should be first + // B is freshly unblocked (never assigned), A is a cobuild retry - B should be first const r3: IteratorResult = await queue.next(); expect(r3.value).toBe(opB); diff --git a/vscode-extensions/playwright-local-browser-server-vscode-extension/README.md b/vscode-extensions/playwright-local-browser-server-vscode-extension/README.md index 4968cb7f8ef..2a93e5a8ed0 100644 --- a/vscode-extensions/playwright-local-browser-server-vscode-extension/README.md +++ b/vscode-extensions/playwright-local-browser-server-vscode-extension/README.md @@ -163,13 +163,13 @@ This extension contributes the following commands: - **Playwright Local Browser Server: Manage Launch Options Allowlist** (`playwright-local-browser-server.manageAllowlist`) - **Playwright Local Browser Server: Show Log** (`playwright-local-browser-server.showLog`) - **Playwright Local Browser Server: Show Settings** (`playwright-local-browser-server.showSettings`) -- **Playwright Local Browser Server: Show Tunnel Menu** (`playwright-local-browser-server.showMenu`) — status bar menu +- **Playwright Local Browser Server: Show Tunnel Menu** (`playwright-local-browser-server.showMenu`) - status bar menu ## Settings -- `playwright-local-browser-server.autoStart` (default: `false`) — automatically starts the tunnel when the extension activates. -- `playwright-local-browser-server.promptBeforeLaunch` (default: `true`) — show a confirmation prompt before launching the browser server with the requested launch options. This helps protect against potentially malicious launch options from compromised environments. -- `playwright-local-browser-server.tunnelPort` (default: `56767`) — port used by the remote tunnel server. +- `playwright-local-browser-server.autoStart` (default: `false`) - automatically starts the tunnel when the extension activates. +- `playwright-local-browser-server.promptBeforeLaunch` (default: `true`) - show a confirmation prompt before launching the browser server with the requested launch options. This helps protect against potentially malicious launch options from compromised environments. +- `playwright-local-browser-server.tunnelPort` (default: `56767`) - port used by the remote tunnel server. ## Notes From 92dc0cf8d28c1038a9c58aa9aecd85e2dfd6f269 Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Fri, 10 Apr 2026 16:40:12 -0700 Subject: [PATCH 3/4] fixup! [heft-sass-plugin] Improve README and sass.json template --- heft-plugins/heft-sass-plugin/src/templates/sass.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/heft-plugins/heft-sass-plugin/src/templates/sass.json b/heft-plugins/heft-sass-plugin/src/templates/sass.json index 259d966519a..2a874148f6e 100644 --- a/heft-plugins/heft-sass-plugin/src/templates/sass.json +++ b/heft-plugins/heft-sass-plugin/src/templates/sass.json @@ -28,7 +28,7 @@ /** * Optional additional folders to which Sass typings should be output. Useful when publishing typings - * alongside compiled output (e.g. "lib-esm"). + * alongside compiled output (e.g. "lib-dts"). */ // "secondaryGeneratedTsFolders": [], From cacb1042b8fce2784cb744fa9873158121e8444f Mon Sep 17 00:00:00 2001 From: Ian Clanton-Thuon Date: Fri, 10 Apr 2026 16:50:16 -0700 Subject: [PATCH 4/4] Add a unit test for the sass template. --- .../heft-sass-plugin/src/test/tempate.test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 heft-plugins/heft-sass-plugin/src/test/tempate.test.ts diff --git a/heft-plugins/heft-sass-plugin/src/test/tempate.test.ts b/heft-plugins/heft-sass-plugin/src/test/tempate.test.ts new file mode 100644 index 00000000000..afdab02292a --- /dev/null +++ b/heft-plugins/heft-sass-plugin/src/test/tempate.test.ts @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license. +// See LICENSE in the project root for license information. + +import { FileSystem, JsonFile, JsonSchema } from '@rushstack/node-core-library'; + +import schema from '../schemas/heft-sass-plugin.schema.json'; +import type { ISassConfigurationJson } from '../SassPlugin'; + +describe('sass.json template', () => { + it('should match the schema', async () => { + const templateText: string = await FileSystem.readFileAsync(`${__dirname}/../templates/sass.json`); + let uncommentedTemplateText: string = templateText.replace(/$\s*\/\/\s*/gm, ''); + uncommentedTemplateText = uncommentedTemplateText.replace('"extends":', ',"extends":'); + const template: ISassConfigurationJson = JsonFile.parseString(uncommentedTemplateText); + const jsonSchema: JsonSchema = JsonSchema.fromLoadedObject(schema); + expect(() => jsonSchema.validateObject(template, `${__dirname}/../templates/sass.json`)).not.toThrow(); + }); +});