diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index c0358f4..b1fade3 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -23,7 +23,7 @@ jobs: strategy: matrix: os: [ubuntu-latest] - node-version: [lts/*] + node-version: ["lts/*"] runs-on: ${{ matrix.os }} @@ -32,15 +32,17 @@ jobs: cancel-in-progress: true steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 with: fetch-depth: 0 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: ${{ matrix.node-version }} + architecture: "x64" cache: "yarn" + cache-dependency-path: "yarn.lock" - name: Install dependencies run: yarn --frozen-lockfile @@ -54,7 +56,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, windows-latest, macos-latest] - node-version: [10.x, 12.x, 14.x, 16.x, 18.x, 20.x] + node-version: ["10", "12", "14", "16", "18", "20"] webpack-version: [latest] runs-on: ${{ matrix.os }} @@ -68,13 +70,15 @@ jobs: if: matrix.os == 'windows-latest' run: git config --global core.autocrlf input - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Use Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: - node-version: ${{ matrix.node-version }} + node-version: "${{ matrix.node-version }}" + architecture: "x64" cache: "yarn" + cache-dependency-path: "yarn.lock" - name: Install dependencies run: yarn --frozen-lockfile diff --git a/src/index.js b/src/index.js index eeaef93..3ead814 100644 --- a/src/index.js +++ b/src/index.js @@ -590,6 +590,7 @@ module.exports = (options = {}) => { } }); + const localRules = new Set(); root.walkRules((rule) => { if ( rule.parent && @@ -605,13 +606,23 @@ module.exports = (options = {}) => { context.options = options; context.localAliasMap = localAliasMap; - if (pureMode && context.hasPureGlobals) { + if ( + pureMode && + context.hasPureGlobals && + !localRules.has(rule.parent) + ) { throw rule.error( 'Selector "' + rule.selector + '" is not pure ' + "(pure selectors must contain at least one local class or id)" ); + } else { + // Once a parent is pure all children are also pure + // For example the span inside this .foo is also pure + // although it is not local itself: + // .foo { span { color: red; } } + localRules.add(rule); } rule.selector = context.selector; diff --git a/test/index.test.js b/test/index.test.js index 99285fc..5f2eb36 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -868,6 +868,30 @@ const tests = [ options: { mode: "pure" }, error: /is not pure/, }, + { + name: "consider nonlocal children of a pure parent as pure", + input: ".foo { span { a_value: some-value; } }", + options: { mode: "pure" }, + expected: ":local(.foo) { span { a_value: some-value; } }", + }, + { + name: "consider nested nonlocal children of a pure parent as pure", + input: ".foo { span { a { a_value: some-value; } } }", + options: { mode: "pure" }, + expected: ":local(.foo) { span { a { a_value: some-value; } } }", + }, + { + name: "throw on mixed parents", + input: ".foo, html { span { a_value: some-value; } }", + options: { mode: "pure" }, + error: /is not pure/, + }, + { + name: "throw on global styles with a local selector", + input: `html { a_value: some-value; .foo { a_value: some-value; } }`, + options: { mode: "pure" }, + error: /is not pure/, + }, { name: "css nesting", input: `