diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0e1be2eaeb..5205314524 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,10 +3,6 @@ name: CI defaults: run: {shell: bash} -env: - # TODO(jathak): Update this to Node 18 once unit tests are fixed. - NODE_VERSION: 14 - on: push: {branches: [main, feature.*]} pull_request: @@ -18,8 +14,9 @@ jobs: steps: - uses: actions/checkout@v3 + # TODO(jathak): Update this to 'lts/*' (the latest lts version) once unit tests are fixed. - uses: actions/setup-node@v3 - with: {node-version: "${{ env.NODE_VERSION }}"} + with: {node-version: 14} - uses: dart-lang/setup-dart@v1 with: {sdk: stable} - run: npm install @@ -32,7 +29,7 @@ jobs: steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 - with: {node-version: "${{ env.NODE_VERSION }}"} + with: {node-version: 'lts/*'} - run: npm install - run: npm run lint @@ -43,7 +40,7 @@ jobs: steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 - with: {node-version: "${{ env.NODE_VERSION }}"} + with: {node-version: 'lts/*'} - run: npm install - run: npm run lint-spec @@ -59,7 +56,7 @@ jobs: steps: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 - with: {node-version: "${{ env.NODE_VERSION }}"} + with: {node-version: 'lts/*'} - run: npm install - uses: ./.github/util/dart-sass @@ -69,10 +66,7 @@ jobs: - name: Run specs run: npm run sass-spec -- --dart dart-sass - - # The versions should be kept up-to-date with the latest LTS Node releases. - # They next need to be rotated October 2021. See - # https://github.com/nodejs/Release. + js_api_dart_sass: name: "JS API | Pure JS | Node ${{ matrix.node_version }} | ${{ matrix.os }}" runs-on: "${{ matrix.os }}" @@ -82,13 +76,15 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - node_version: [18] + node_version: ['lts/*'] # Only test LTS versions on Ubuntu include: - os: ubuntu-latest - node_version: 14 + node_version: lts/-1 - os: ubuntu-latest - node_version: 16 + node_version: lts/-2 + - os: ubuntu-latest + node_version: lts/-3 steps: - uses: actions/checkout@v3 @@ -136,9 +132,6 @@ jobs: --sassSassRepo dart-sass/build/language env: {CHROME_EXECUTABLE: chrome} - # The versions should be kept up-to-date with the latest LTS Node releases. - # They next need to be rotated October 2021. See - # https://github.com/nodejs/Release. js_api_sass_embedded: name: "JS API | Embedded | Node ${{ matrix.node_version }} | ${{ matrix.os }}" runs-on: "${{ matrix.os }}" @@ -148,13 +141,15 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, windows-latest, macos-latest] - node_version: [18] + node_version: ['lts/*'] # Only test LTS versions on Ubuntu include: - os: ubuntu-latest - node_version: 14 + node_version: lts/-1 + - os: ubuntu-latest + node_version: lts/-2 - os: ubuntu-latest - node_version: 16 + node_version: lts/-3 steps: - uses: actions/checkout@v3 diff --git a/js-api-spec/compiler.node.test.ts b/js-api-spec/compiler.node.test.ts new file mode 100644 index 0000000000..8af5c13b8f --- /dev/null +++ b/js-api-spec/compiler.node.test.ts @@ -0,0 +1,140 @@ +// Copyright 2024 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import type {AsyncCompiler, Compiler, CompileResult, Importer} from 'sass'; +import {initAsyncCompiler, initCompiler} from 'sass'; + +import { + asyncImporters, + functions, + getLogger, + getTriggeredImporter, + importers, +} from './compiler.test'; +import {sandbox} from './sandbox'; +import {URL} from './utils'; + +describe('Compiler', () => { + let compiler: Compiler; + + beforeEach(() => { + compiler = initCompiler(); + }); + + afterEach(() => { + compiler.dispose(); + }); + + describe('compile', () => { + it('performs complete compilations', () => + sandbox(dir => { + const logger = getLogger(); + dir.write({'input.scss': '@import "bar"; .fn {value: foo(bar)}'}); + const result = compiler.compile(dir('input.scss'), { + importers, + functions, + logger, + }); + expect(result.css).toEqualIgnoringWhitespace( + '.import {value: bar;} .fn {value: "bar";}' + ); + expect(logger.debug).toHaveBeenCalledTimes(1); + })); + + it('performs compilations in callbacks', () => + sandbox(dir => { + dir.write({'input-nested.scss': 'x {y: z}'}); + const nestedImporter: Importer = { + canonicalize: () => new URL('foo:bar'), + load: () => ({ + contents: compiler.compile(dir('input-nested.scss')).css, + syntax: 'scss', + }), + }; + dir.write({'input.scss': '@import "nested"; a {b: c}'}); + const result = compiler.compile(dir('input.scss'), { + importers: [nestedImporter], + }); + expect(result.css).toEqualIgnoringWhitespace('x {y: z;} a {b: c;}'); + })); + + it('throws after being disposed', () => + sandbox(dir => { + dir.write({'input.scss': '$a: b; c {d: $a}'}); + compiler.dispose(); + expect(() => compiler.compile(dir('input.scss'))).toThrowError(); + })); + }); +}); + +describe('AsyncCompiler', () => { + let compiler: AsyncCompiler; + + beforeEach(async () => { + compiler = await initAsyncCompiler(); + }); + + afterEach(async () => { + await compiler.dispose(); + }); + + describe('compileAsync', () => { + it( + 'handles multiple concurrent compilations', + () => + sandbox(async dir => { + const runs = 1000; // Number of concurrent compilations to run + const logger = getLogger(); + const compilations = Array(runs) + .fill(0) + .map((_, i) => { + const filename = `input-${i}.scss`; + dir.write({ + [filename]: `@import "${i}"; .fn {value: foo(${i})}`, + }); + return compiler.compileAsync(dir(filename), { + importers: asyncImporters, + functions, + logger, + }); + }); + Array.from(await Promise.all(compilations)) + .map((result: CompileResult) => result.css) + .forEach((result, i) => { + expect(result).toEqualIgnoringWhitespace( + `.import {value: ${i};} .fn {value: "${i}";}` + ); + }); + expect(logger.debug).toHaveBeenCalledTimes(runs); + }), + 40_000 // Increase timeout for slow CI + ); + + it('throws after being disposed', () => + sandbox(async dir => { + dir.write({'input.scss': '$a: b; c {d: $a}'}); + await compiler.dispose(); + expect(() => compiler.compileAsync(dir('input.scss'))).toThrowError(); + })); + + it('waits for compilations to finish before disposing', () => + sandbox(async dir => { + let completed = false; + dir.write({'input.scss': '@import "slow"'}); + const {importer, triggerComplete} = getTriggeredImporter( + () => (completed = true) + ); + const compilation = compiler.compileAsync(dir('input.scss'), { + importers: [importer], + }); + const disposalPromise = compiler.dispose(); + expect(completed).toBeFalse(); + triggerComplete(); + + await disposalPromise; + expect(completed).toBeTrue(); + await expectAsync(compilation).toBeResolved(); + })); + }); +}); diff --git a/js-api-spec/compiler.test.ts b/js-api-spec/compiler.test.ts new file mode 100644 index 0000000000..065f3e7e49 --- /dev/null +++ b/js-api-spec/compiler.test.ts @@ -0,0 +1,201 @@ +// Copyright 2024 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import type {CompileResult, Importer} from 'sass'; +import { + initAsyncCompiler, + initCompiler, + SassString, + AsyncCompiler, + Compiler, +} from 'sass'; + +import {spy, URL} from './utils'; + +export const functions = { + 'foo($args)': (args: unknown) => new SassString(`${args}`), +}; + +export const importers: Array = [ + { + canonicalize: url => new URL(`u:${url}`), + load: url => ({ + contents: `.import {value: ${url.pathname}} @debug "imported";`, + syntax: 'scss' as const, + }), + }, +]; + +export const asyncImporters: Array = [ + { + canonicalize: url => Promise.resolve(new URL(`u:${url}`)), + load: url => Promise.resolve(importers[0].load(url)), + }, +]; + +export const getLogger = () => ({debug: spy(() => {})}); + +/* A triggered importer that executes a callback after a trigger is called */ +export function getTriggeredImporter(callback: () => void): { + importer: Importer; + triggerComplete: () => void; +} { + let promiseResolve: (value: unknown) => void; + const awaitedPromise = new Promise(resolve => { + promiseResolve = resolve; + }); + return { + importer: { + canonicalize: async () => new URL('foo:bar'), + load: async () => { + await awaitedPromise; + callback(); + return {contents: '', syntax: 'scss' as const}; + }, + }, + triggerComplete: () => promiseResolve(undefined), + }; +} + +describe('Compiler', () => { + let compiler: Compiler; + + beforeEach(() => { + compiler = initCompiler(); + }); + + afterEach(() => { + compiler.dispose(); + }); + + describe('compileString', () => { + it('performs complete compilations', () => { + const logger = getLogger(); + const result = compiler.compileString( + '@import "bar"; .fn {value: foo(baz)}', + {importers, functions, logger} + ); + expect(result.css).toEqualIgnoringWhitespace( + '.import {value: bar;} .fn {value: "baz";}' + ); + expect(logger.debug).toHaveBeenCalledTimes(1); + }); + + it('performs compilations in callbacks', () => { + const nestedImporter: Importer = { + canonicalize: () => new URL('foo:bar'), + load: () => ({ + contents: compiler.compileString('x {y: z}').css, + syntax: 'scss', + }), + }; + const result = compiler.compileString('@import "nested"; a {b: c}', { + importers: [nestedImporter], + }); + expect(result.css).toEqualIgnoringWhitespace('x {y: z;} a {b: c;}'); + }); + + it('throws after being disposed', () => { + compiler.dispose(); + expect(() => compiler.compileString('$a: b; c {d: $a}')).toThrowError(); + }); + + it('succeeds after a compilation failure', () => { + expect(() => compiler.compileString('a')).toThrowSassException({ + includes: 'expected "{"', + }); + const result2 = compiler.compileString('x {y: z}'); + expect(result2.css).toEqualIgnoringWhitespace('x {y: z;}'); + }); + }); + + it('errors if constructor invoked directly', () => { + // Strip types to allow calling private constructor. + class Untyped {} + const UntypedCompiler = Compiler as unknown as typeof Untyped; + expect(() => new UntypedCompiler()).toThrowError( + /Compiler can not be directly constructed/ + ); + }); +}); + +describe('AsyncCompiler', () => { + let compiler: AsyncCompiler; + const runs = 1000; // Number of concurrent compilations to run + + beforeEach(async () => { + compiler = await initAsyncCompiler(); + }); + + afterEach(async () => { + await compiler.dispose(); + }); + + describe('compileStringAsync', () => { + it('handles multiple concurrent compilations', async () => { + const logger = getLogger(); + const compilations = Array(runs) + .fill(0) + .map((_, i) => + compiler.compileStringAsync( + `@import "${i}"; .fn {value: foo(${i})}`, + {importers: asyncImporters, functions, logger} + ) + ); + Array.from(await Promise.all(compilations)) + .map((result: CompileResult) => result.css) + .forEach((result, i) => { + expect(result).toEqualIgnoringWhitespace( + `.import {value: ${i};} .fn {value: "${i}";}` + ); + }); + expect(logger.debug).toHaveBeenCalledTimes(runs); + }, 15_000); // Increase timeout for slow CI + + it('throws after being disposed', async () => { + await compiler.dispose(); + expect(() => + compiler.compileStringAsync('$a: b; c {d: $a}') + ).toThrowError(); + }); + + it('waits for compilations to finish before disposing', async () => { + let completed = false; + const {importer, triggerComplete} = getTriggeredImporter( + () => (completed = true) + ); + const compilation = compiler.compileStringAsync('@import "slow"', { + importers: [importer], + }); + + const disposalPromise = compiler.dispose(); + expect(completed).toBeFalse(); + triggerComplete(); + + await disposalPromise; + expect(completed).toBeTrue(); + await expectAsync(compilation).toBeResolved(); + }); + + it('succeeds after a compilation failure', async () => { + expectAsync( + async () => await compiler.compileStringAsync('a') + ).toThrowSassException({ + includes: 'expected "{"', + }); + + const result2 = await compiler.compileStringAsync('x {y: z}'); + expect(result2.css).toEqualIgnoringWhitespace('x {y: z;}'); + }); + }); + + it('errors if constructor invoked directly', () => { + // Strip types to allow calling private constructor. + class Untyped {} + const UntypedAsyncCompiler = AsyncCompiler as unknown as typeof Untyped; + expect(() => new UntypedAsyncCompiler()).toThrowError( + /AsyncCompiler can not be directly constructed/ + ); + }); +}); diff --git a/js-api-spec/importer.test.ts b/js-api-spec/importer.test.ts index b632f90c25..f2bd971542 100644 --- a/js-api-spec/importer.test.ts +++ b/js-api-spec/importer.test.ts @@ -7,9 +7,10 @@ import { compileStringAsync, CanonicalizeContext, Importer, + NodePackageImporter, } from 'sass'; -import {sassImpl, URL} from './utils'; +import {sassImpl, runOnlyForImpl, URL} from './utils'; it('uses an importer to resolve an @import', () => { const result = compileString('@import "orange";', { @@ -763,6 +764,12 @@ it('throws an ArgumentError when the result sourceMapUrl is missing a scheme', ( }); }); +runOnlyForImpl('browser', () => { + it('node package loader throws error in browser', () => { + expect(() => new NodePackageImporter()).toThrow(); + }); +}); + /** * Returns an importer that asserts that `fromImport` is `expected`, and * otherwise imports exclusively empty stylesheets. diff --git a/js-api-spec/node-package-importer.node.test.ts b/js-api-spec/node-package-importer.node.test.ts new file mode 100644 index 0000000000..f99d2ce4f1 --- /dev/null +++ b/js-api-spec/node-package-importer.node.test.ts @@ -0,0 +1,952 @@ +// Copyright 2024 Google Inc. Use of this source code is governed by an +// MIT-style license that can be found in the LICENSE file or at +// https://opensource.org/licenses/MIT. + +import { + compile, + compileAsync, + compileString, + compileStringAsync, + render, + renderSync, + NodePackageImporter, + LegacyException, + LegacyResult, +} from 'sass'; + +import {sandbox} from './sandbox'; +import {spy} from './utils'; + +import {fileURLToPath} from 'url'; + +const testPackageImporter = ({ + input, + output, + files, + entryPoint, +}: { + input: string; + output: string; + files: {[path: string]: string}; + entryPoint?: string; +}) => + sandbox(dir => { + dir.write(files); + return dir.chdir(() => { + try { + const result = compileString(input, { + importers: [new NodePackageImporter(entryPoint)], + }); + expect(result.css).toEqualIgnoringWhitespace(output); + } catch (error) { + // Log error to include full stack trace + console.error(error); + throw error; + } + }); + }); + +describe('Node Package Importer', () => { + describe('resolves conditional exports', () => { + ['sass', 'style', 'default'].forEach(key => { + it(`${key} for root `, () => + testPackageImporter({ + input: '@use "pkg:foo";', + output: 'a {b: c;}', + files: { + 'node_modules/foo/src/sass/_styles.scss': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: { + '.': { + [key]: './src/sass/_styles.scss', + }, + }, + }), + }, + })); + + it(`${key} for root without '.'`, () => + testPackageImporter({ + input: '@use "pkg:foo";', + output: 'a {b: c;}', + files: { + 'node_modules/foo/src/sass/_styles.scss': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: { + [key]: './src/sass/_styles.scss', + }, + }), + }, + })); + + it(`${key} with subpath`, () => + testPackageImporter({ + input: '@use "pkg:foo/styles";', + output: 'a {b: c;}', + files: { + 'node_modules/foo/src/sass/_styles.scss': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: { + './_styles.scss': { + [key]: './src/sass/_styles.scss', + }, + }, + }), + }, + })); + + it(`${key} with index`, () => + testPackageImporter({ + input: '@use "pkg:foo/subdir";', + output: 'a {b: c;}', + files: { + 'node_modules/foo/src/sass/subdir/index.scss': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: { + './subdir/index.scss': { + [key]: './src/sass/subdir/index.scss', + }, + }, + }), + }, + })); + }); + + [ + './color', + './color.css', + './color.scss', + './color.sass', + './_color', + './_color.css', + './_color.scss', + './_color.sass', + './color/index', + './color/index.css', + './color/index.scss', + './color/index.sass', + './color/_index', + './color/_index.css', + './color/_index.scss', + './color/_index.sass', + ].forEach(key => { + it(`resolves subpath key ${key} without _ or extension`, () => + testPackageImporter({ + input: '@use "pkg:foo/color";', + output: 'a {b: c;}', + files: { + 'node_modules/foo/src/sass/_color.scss': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: { + [key]: { + sass: './src/sass/_color.scss', + }, + }, + }), + }, + })); + }); + + ['./color.scss', './_color.scss'].forEach(key => { + it(`resolves subpath key ${key} with extension`, () => + testPackageImporter({ + input: '@use "pkg:foo/color.scss";', + output: 'a {b: c;}', + files: { + 'node_modules/foo/src/sass/_color.scss': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: { + [key]: { + sass: './src/sass/_color.scss', + }, + }, + }), + }, + })); + }); + + it('compiles with first conditional match found', () => + testPackageImporter({ + input: '@use "pkg:foo";', + output: 'a {from: sassCondition;}', + files: { + 'node_modules/foo/src/sass/_styles.scss': 'd {from: styleCondition}', + 'node_modules/foo/src/sass/_sass.scss': 'a {from: sassCondition}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: { + '.': { + sass: './src/sass/_sass.scss', + style: './src/sass/_styles.scss', + }, + }, + }), + }, + })); + + it('throws if multiple exported paths match', () => + sandbox(dir => { + dir.write({ + 'node_modules/foo/src/sass/_styles.scss': 'd {e: f}', + 'node_modules/foo/src/sass/_other.scss': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: { + './index.scss': {sass: './src/sass/_other.scss'}, + './_index.sass': {sass: './src/sass/_styles.scss'}, + }, + }), + }); + return dir.chdir(() => { + expect(() => + compileString('@use "pkg:foo";', { + importers: [new NodePackageImporter()], + }) + ).toThrowSassException({ + includes: 'multiple potential resolutions', + }); + }); + })); + + it('throws if resolved path does not have a valid extension', () => + sandbox(dir => { + dir.write({ + 'node_modules/foo/src/sass/_styles.txt': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: { + './index.scss': {sass: './src/sass/_styles.txt'}, + }, + }), + }); + return dir.chdir(() => { + expect(() => + compileString('@use "pkg:foo";', { + importers: [new NodePackageImporter()], + }) + ).toThrowSassException({ + includes: "_styles.txt', which is not a '.scss'", + }); + }); + })); + + it('resolves string export', () => + testPackageImporter({ + input: '@use "pkg:foo";', + output: 'a {b: c;}', + files: { + 'node_modules/foo/src/sass/_styles.scss': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: './src/sass/_styles.scss', + }), + }, + })); + + describe('wildcards', () => { + it('resolves with partial', () => + testPackageImporter({ + input: '@use "pkg:foo/styles";', + output: 'a {b: c;}', + files: { + 'node_modules/foo/src/sass/_styles.scss': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: {'./*.scss': './src/sass/*.scss'}, + }), + }, + })); + + it('resolves with full wildcard path and sass conditional export', () => + testPackageImporter({ + input: '@use "pkg:foo/styles";', + output: 'a {b: c;}', + files: { + 'node_modules/foo/src/sass/_styles.scss': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: {'./*': {sass: './src/sass/*'}}, + }), + }, + })); + + it('resolves file extension variant', () => + testPackageImporter({ + input: '@use "pkg:foo/sass/styles";', + output: 'a {b: c;}', + files: { + 'node_modules/foo/src/sass/_styles.scss': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: {'./sass/*': './src/sass/*'}, + }), + }, + })); + + it('resolves multipart paths', () => + testPackageImporter({ + input: '@use "pkg:foo/sass/styles";', + output: 'a {b: c;}', + files: { + 'node_modules/foo/src/sass/_styles.scss': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: {'./*.scss': './src/*.scss'}, + }), + }, + })); + + it('throws if multiple wildcard exports match', () => + sandbox(dir => { + dir.write({ + 'node_modules/foo/src/sass/styles.scss': 'a {b: c}', + 'node_modules/foo/src/sass/_styles.scss': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: {'./*.scss': './src/sass/*.scss'}, + }), + }); + return dir.chdir(() => { + expect( + () => + compileString('@use "pkg:foo/styles";', { + importers: [new NodePackageImporter()], + }).css + ).toThrowSassException({ + includes: 'multiple potential resolutions', + }); + }); + })); + }); + }); + + it('throws if package.json is not json', () => + sandbox(dir => { + dir.write({ + 'node_modules/foo/package.json': 'invalid json', + }); + return dir.chdir(() => { + expect(() => + compileString('@use "pkg:foo";', { + importers: [new NodePackageImporter()], + }) + ).toThrowSassException({ + includes: 'Failed to parse', + }); + }); + })); + + describe('without subpath', () => { + it('sass key in package.json', () => + testPackageImporter({ + input: '@use "pkg:foo";', + output: 'a {b: c;}', + files: { + 'node_modules/foo/src/sass/_styles.scss': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + sass: 'src/sass/_styles.scss', + }), + }, + })); + + it('style key in package.json', () => + testPackageImporter({ + input: '@use "pkg:foo";', + output: 'a {b: c;}', + files: { + 'node_modules/foo/src/sass/_styles.scss': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + style: 'src/sass/_styles.scss', + }), + }, + })); + + ['index.scss', 'index.css', '_index.scss', '_index.css'].forEach( + fileName => { + it(`loads from ${fileName}`, () => + testPackageImporter({ + input: '@use "pkg:foo";', + output: 'a {b: c;}', + files: { + [`node_modules/foo/${fileName}`]: 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({}), + }, + })); + } + ); + + ['index.sass', '_index.sass'].forEach(fileName => { + it(`loads from ${fileName}`, () => + testPackageImporter({ + input: '@use "pkg:foo";', + output: 'a {b: c;}', + files: { + [`node_modules/foo/${fileName}`]: 'a \n b: c', + 'node_modules/foo/package.json': JSON.stringify({}), + }, + })); + }); + }); + + it('with subpath, resolves relative to package root', () => + testPackageImporter({ + input: '@use "pkg:bar/src/styles/sass";', + output: 'a {b: c;}', + files: { + 'node_modules/bar/src/styles/sass/index.scss': 'a {b: c}', + 'node_modules/bar/package.json': JSON.stringify({}), + }, + })); + + describe('resolves from packages', () => { + it('resolves from secondary @use', () => + sandbox(dir => { + dir.write({ + 'node_modules/bah/index.scss': 'a {b: c}', + 'node_modules/bah/package.json': JSON.stringify({}), + '_vendor.scss': '@use "pkg:bah";', + }); + return dir.chdir(() => { + const result = compileString('@use "vendor";', { + importers: [ + new NodePackageImporter(), + { + findFileUrl: file => dir.url(file), + }, + ], + }); + expect(result.css).toEqualIgnoringWhitespace('a {b: c;}'); + }); + })); + + it('resolves from secondary @use pkg root', () => + testPackageImporter({ + input: '@use "pkg:bah";', + output: 'a {b: c;}', + files: { + 'node_modules/bah/index.scss': '@use "pkg:bar-secondary";', + 'node_modules/bah/package.json': JSON.stringify({}), + 'node_modules/bar-secondary/index.scss': 'a {b: c}', + 'node_modules/bar-secondary/package.json': JSON.stringify({}), + }, + })); + + it('relative import in package', () => + testPackageImporter({ + input: '@use "pkg:foo";', + output: 'a {b: c;}', + files: { + 'node_modules/foo/scss/styles.scss': '@use "mixins/banner";', + 'node_modules/foo/scss/mixins/_banner.scss': 'a {b: c;}', + 'node_modules/foo/package.json': JSON.stringify({ + sass: 'scss/styles.scss', + }), + }, + })); + + it('resolves most proximate node_module', () => + testPackageImporter({ + input: '@use "pkg:bah";', + output: 'a {from: submodule;}', + files: { + 'node_modules/bah/index.scss': '@use "pkg:bar-proximate";', + 'node_modules/bah/package.json': JSON.stringify({}), + 'node_modules/bar-proximate/index.scss': 'e {from: root}', + 'node_modules/bar-proximate/package.json': JSON.stringify({}), + 'node_modules/bah/node_modules/bar-proximate/index.scss': + 'a {from: submodule;}', + 'node_modules/bah/node_modules/bar-proximate/package.json': + JSON.stringify({}), + }, + })); + + it('resolves most proximate node_module to specified entry point', () => + testPackageImporter({ + input: '@use "pkg:bah";', + output: 'a {from: submodule;}', + files: { + 'subdir/node_modules/bah/index.scss': '@use "pkg:bar-entry";', + 'subdir/node_modules/bah/package.json': JSON.stringify({}), + 'node_modules/bah/index.scss': 'e {from: root}', + 'node_modules/bah/package.json': JSON.stringify({}), + 'subdir/node_modules/bah/node_modules/bar-entry/index.scss': + 'a {from: submodule;}', + 'subdir/node_modules/bah/node_modules/bar-entry/package.json': + JSON.stringify({}), + }, + entryPoint: './subdir', + })); + + it('resolves sub node_module', () => + testPackageImporter({ + input: '@use "pkg:bah";', + output: 'a {b: c;}', + files: { + 'node_modules/bah/index.scss': '@use "pkg:bar-sub";', + 'node_modules/bah/package.json': JSON.stringify({}), + 'node_modules/bah/node_modules/bar-sub/index.scss': 'a {b: c}', + 'node_modules/bah/node_modules/bar-sub/package.json': JSON.stringify( + {} + ), + }, + })); + + it('resolves node_module above cwd', () => + sandbox(dir => { + dir.write({ + 'node_modules/bar-above/index.scss': 'a {b: c}', + 'node_modules/bar-above/package.json': JSON.stringify({}), + }); + return dir.chdir( + () => { + const result = compileString('@use "pkg:bar-above";', { + importers: [new NodePackageImporter()], + }); + return expect(result.css).toEqualIgnoringWhitespace('a {b: c;}'); + }, + {entryPoint: 'deeply/nested/file/'} + ); + })); + + it('resolves with absolute entry point directory', () => + sandbox(dir => { + dir.write({ + 'node_modules/bar-abs/index.scss': 'a {b: c}', + 'node_modules/bar-abs/package.json': JSON.stringify({}), + }); + const entryPoint = fileURLToPath(dir.url()); + return dir.chdir(() => { + const result = compileString('@use "pkg:bar-abs";', { + importers: [new NodePackageImporter(entryPoint)], + }); + return expect(result.css).toEqualIgnoringWhitespace('a {b: c;}'); + }); + })); + + it('resolves in scoped package', () => + testPackageImporter({ + input: '@use "pkg:@foo/bar";', + output: 'a {b: c;}', + files: { + 'node_modules/@foo/bar/src/sass/_styles.scss': 'a {b: c}', + 'node_modules/@foo/bar/package.json': JSON.stringify({ + exports: './src/sass/_styles.scss', + }), + }, + })); + + it('resolves from secondary @use in scoped packages', () => + testPackageImporter({ + input: '@use "pkg:@foo/bah";', + output: 'a {b: c;}', + files: { + 'node_modules/@foo/bah/index.scss': '@use "pkg:@foo/bar";', + 'node_modules/@foo/bah/package.json': JSON.stringify({}), + 'node_modules/@foo/bar/index.scss': 'a {b: c}', + 'node_modules/@foo/bar/package.json': JSON.stringify({}), + }, + })); + + it('fails if no match found', () => { + const canonicalize = spy((url: string) => { + expect(url).toStartWith('pkg:'); + return null; + }); + sandbox(dir => { + return dir.chdir(() => { + expect(() => + compileString('@use "pkg:bah";', { + importers: [ + new NodePackageImporter(), + { + canonicalize, + load: () => null, + }, + ], + }) + ).toThrowSassException({ + includes: "Can't find stylesheet to import", + }); + expect(canonicalize).toHaveBeenCalled(); + }); + }); + }); + }); + + it('faked Node Package Importer fails', () => + sandbox(dir => { + dir.write({'foo/index.scss': 'a {from: dir}'}); + + expect(() => + compileString('@use "pkg:foo";', { + importers: [Symbol() as unknown as NodePackageImporter], + }) + ).toThrow(); + })); + + it('fails with invalid package.json exports', () => + sandbox(dir => { + dir.write({ + 'node_modules/foo/src/sass/_styles.scss': 'a {b: c}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: { + '.': { + sass: './src/sass/_styles.scss', + }, + sass: './src/sass/_styles.scss', + }, + }), + }); + return dir.chdir(() => { + expect(() => + compileString('@use "pkg:foo";', { + importers: [new NodePackageImporter()], + }) + ).toThrowSassException({ + includes: 'can not have both conditions and paths', + }); + }); + })); + + describe('compilation methods', () => { + it('compile', () => + sandbox(dir => { + dir.write({ + 'node_modules/bah/index.scss': 'a {b: c}', + 'node_modules/bah/package.json': JSON.stringify({}), + '_index.scss': '@use "pkg:bah";', + }); + return dir.chdir(() => { + const result = compile('./_index.scss', { + importers: [ + new NodePackageImporter(), + { + findFileUrl: file => dir.url(file), + }, + ], + }); + expect(result.css).toEqualIgnoringWhitespace('a {b: c;}'); + }); + })); + + it('compile with nested path', () => + sandbox(dir => { + dir.write({ + 'deeply/nested/node_modules/bah/index.scss': 'a {b: c}', + 'deeply/nested/node_modules/bah/package.json': JSON.stringify({}), + 'deeply/nested/_index.scss': '@use "pkg:bah";', + 'node_modules/bah/index.scss': 'from {root: notPath}', + 'node_modules/bah/package.json': JSON.stringify({}), + }); + return dir.chdir(() => { + const result = compile('./deeply/nested/_index.scss', { + importers: [ + new NodePackageImporter(), + { + findFileUrl: file => dir.url(file), + }, + ], + }); + expect(result.css).toEqualIgnoringWhitespace('a {b: c;}'); + }); + })); + + it('compileString', () => + sandbox(dir => { + dir.write({ + 'node_modules/bah/index.scss': 'a {b: c}', + 'node_modules/bah/package.json': JSON.stringify({}), + }); + return dir.chdir(() => { + const result = compileString('@use "pkg:bah";', { + importers: [new NodePackageImporter()], + }); + expect(result.css).toEqualIgnoringWhitespace('a {b: c;}'); + }); + })); + + it('compileString with url', () => + sandbox(dir => { + dir.write({ + 'deeply/nested/node_modules/bah/index.scss': 'a {b: c}', + 'deeply/nested/node_modules/bah/package.json': JSON.stringify({}), + 'deeply/nested/_index.scss': '@use "pkg:bah";', + 'node_modules/bah/index.scss': 'from {root: notPath}', + 'node_modules/bah/package.json': JSON.stringify({}), + }); + return dir.chdir(() => { + const result = compileString('@use "pkg:bah";', { + importers: [new NodePackageImporter()], + url: dir.url('deeply/nested/_index.scss'), + }); + expect(result.css).toEqualIgnoringWhitespace('a {b: c;}'); + }); + })); + + it('compileString without url uses cwd', () => + sandbox(dir => { + dir.write({ + 'deeply/nested/node_modules/bah/index.scss': 'from {nested: path}', + 'deeply/nested/node_modules/bah/package.json': JSON.stringify({}), + 'deeply/nested/_index.scss': '@use "pkg:bah";', + 'node_modules/bah/index.scss': 'a {b: c}', + 'node_modules/bah/package.json': JSON.stringify({}), + }); + return dir.chdir(() => { + const result = compileString('@use "pkg:bah";', { + importers: [new NodePackageImporter()], + }); + expect(result.css).toEqualIgnoringWhitespace('a {b: c;}'); + }); + })); + + it('compileAsync', () => + sandbox(dir => { + dir.write({ + 'node_modules/bah/index.scss': 'a {b: c}', + 'node_modules/bah/package.json': JSON.stringify({}), + '_index.scss': '@use "pkg:bah";', + }); + return dir.chdir(async () => { + const result = await compileAsync('./_index.scss', { + importers: [ + new NodePackageImporter(), + { + findFileUrl: file => dir.url(file), + }, + ], + }); + expect(result.css).toEqualIgnoringWhitespace('a { b: c;}'); + return result; + }); + })); + + it('compileStringAsync', () => + sandbox(dir => { + dir.write({ + 'node_modules/bah/index.scss': 'a {b: c}', + 'node_modules/bah/package.json': JSON.stringify({}), + }); + return dir.chdir(async () => { + const result = await compileStringAsync('@use "pkg:bah";', { + importers: [new NodePackageImporter()], + }); + expect(result.css).toEqualIgnoringWhitespace('a {b: c;}'); + return result; + }); + })); + + it('render string', () => + sandbox(dir => { + dir.write({ + 'node_modules/bah/index.scss': 'a {b: c}', + 'node_modules/bah/package.json': JSON.stringify({}), + }); + return dir.chdir(async () => { + return await new Promise(resolve => { + render( + { + data: '@use "pkg:bah"', + pkgImporter: new NodePackageImporter(), + }, + (err?: LegacyException, result?: LegacyResult) => { + expect(err).toBeFalsy(); + expect(result!.css.toString()).toEqualIgnoringWhitespace( + 'a { b: c; }' + ); + resolve(undefined); + } + ); + }); + }); + })); + + it('render file', () => + sandbox(dir => { + dir.write({ + 'node_modules/bah/index.scss': 'a {b: c}', + 'node_modules/bah/package.json': JSON.stringify({}), + 'index.scss': '@use "pkg:bah";', + }); + return dir.chdir(async () => { + return await new Promise(resolve => { + render( + { + file: 'index.scss', + pkgImporter: new NodePackageImporter(), + }, + (err?: LegacyException, result?: LegacyResult) => { + expect(err).toBeFalsy(); + expect(result!.css.toString()).toEqualIgnoringWhitespace( + 'a { b: c; }' + ); + resolve(undefined); + } + ); + }); + }); + })); + + it('renderSync file', () => + sandbox(dir => { + dir.write({ + 'node_modules/bah/index.scss': 'a {b: c}', + 'node_modules/bah/package.json': JSON.stringify({}), + 'index.scss': '@use "pkg:bah";', + }); + return dir.chdir(() => { + const result = renderSync({ + file: 'index.scss', + pkgImporter: new NodePackageImporter(), + }).css.toString(); + expect(result).toEqualIgnoringWhitespace('a { b: c;}'); + }); + })); + + it('renderSync data', () => + sandbox(dir => { + dir.write({ + 'node_modules/bah/index.scss': 'a {b: c}', + 'node_modules/bah/package.json': JSON.stringify({}), + }); + return dir.chdir(() => { + const result = renderSync({ + data: '@use "pkg:bah"', + pkgImporter: new NodePackageImporter(), + }).css.toString(); + expect(result).toEqualIgnoringWhitespace('a { b: c;}'); + }); + })); + }); + + describe('rejects invalid URLs', () => { + it('with an absolute path', () => { + expect(() => + compileString('@use "pkg:/absolute";', { + importers: [new NodePackageImporter()], + }) + ).toThrowSassException({includes: 'must not begin with /'}); + }); + + it('with a host', () => { + expect(() => + compileString('@use "pkg://host/library";', { + importers: [new NodePackageImporter()], + }) + ).toThrowSassException({ + includes: 'must not have a host, port, username or password', + }); + }); + + it('with username and password', () => { + expect(() => + compileString('@use "pkg://user:password@library/path" as library;', { + importers: [new NodePackageImporter()], + }) + ).toThrowSassException({ + includes: 'must not have a host, port, username or password', + }); + }); + + it('with port', () => { + expect(() => + compileString('@use "pkg://host:8080/library";', { + importers: [new NodePackageImporter()], + }) + ).toThrowSassException({ + includes: 'must not have a host, port, username or password', + }); + }); + + it('with an empty path', () => { + expect(() => + // Throws `default namespace "" is not a valid Sass identifier` without + // the `as` clause. + compileString('@use "pkg:" as pkg;', { + importers: [new NodePackageImporter()], + }) + ).toThrowSassException({includes: 'must not have an empty path'}); + }); + + it('with a query', () => { + expect(() => + compileString('@use "pkg:library?query";', { + importers: [new NodePackageImporter()], + }) + ).toThrowSassException({includes: 'must not have a query or fragment'}); + }); + + it('with a fragment', () => { + expect(() => + compileString('@use "pkg:library#fragment";', { + importers: [new NodePackageImporter()], + }) + ).toThrowSassException({includes: 'must not have a query or fragment'}); + }); + }); + + describe('package name rules', () => { + it('no match starting with a .', () => { + const canonicalize = spy((url: string) => { + expect(url).toStartWith('pkg:.'); + return null; + }); + // Throws `default namespace "" is not a valid Sass identifier` without + // the `as` clause. + expect(() => + compileString('@use "pkg:.library" as library;', { + importers: [ + new NodePackageImporter(), + {canonicalize, load: () => null}, + ], + }) + ).toThrowSassException({ + includes: "Can't find stylesheet to import", + }); + expect(canonicalize).toHaveBeenCalled(); + }); + + it('no match with scope but no segment', () => { + const canonicalize = spy((url: string) => { + expect(url).toStartWith('pkg:@library'); + return null; + }); + expect(() => + compileString('@use "pkg:@library" as library;', { + importers: [ + new NodePackageImporter(), + {canonicalize, load: () => null}, + ], + }) + ).toThrowSassException({ + includes: "Can't find stylesheet to import", + }); + expect(canonicalize).toHaveBeenCalled(); + }); + + it('no match with escaped %', () => { + const canonicalize = spy((url: string) => { + expect(url).toStartWith('pkg:library%'); + return null; + }); + expect(() => + compileString('@use "pkg:library%" as library;', { + importers: [ + new NodePackageImporter(), + {canonicalize, load: () => null}, + ], + }) + ).toThrowSassException({ + includes: "Can't find stylesheet to import", + }); + expect(canonicalize).toHaveBeenCalled(); + }); + + it('passes with parsed %', () => + testPackageImporter({ + input: '@use "pkg:%66oo";', + output: 'a {from: sassCondition;}', + files: { + 'node_modules/foo/src/sass/_sass.scss': 'a {from: sassCondition}', + 'node_modules/foo/package.json': JSON.stringify({ + exports: { + '.': { + sass: './src/sass/_sass.scss', + }, + }, + }), + }, + })); + }); +}); diff --git a/js-api-spec/sandbox.ts b/js-api-spec/sandbox.ts index 64a198fc11..6ac8be899a 100644 --- a/js-api-spec/sandbox.ts +++ b/js-api-spec/sandbox.ts @@ -32,7 +32,7 @@ export async function sandbox( ); } try { - await test( + return await test( Object.assign((...paths: string[]) => p.join(testDir, ...paths), { root: testDir, url: (...paths: string[]) => pathToFileURL(p.join(testDir, ...paths)), @@ -49,13 +49,22 @@ export async function sandbox( fs.writeFileSync(fullPath, contents); } }, - chdir: (callback: () => unknown) => { + chdir: async ( + callback: () => unknown, + options?: {entryPoint: string} + ) => { const oldPath = process.cwd(); process.chdir(testDir); + const oldEntryPoint = require.main?.filename; + if (oldEntryPoint) { + const filename = options?.entryPoint || p.basename(oldEntryPoint); + require.main!.filename = `${testDir}/${filename}`; + } try { - return callback(); + return await callback(); } finally { process.chdir(oldPath); + if (oldEntryPoint) require.main!.filename = oldEntryPoint; } }, }) @@ -68,7 +77,7 @@ export async function sandbox( } /** The directory object passed to `sandbox`'s callback. */ -interface SandboxDirectory { +export interface SandboxDirectory { /** The root of the sandbox. */ readonly root: string; @@ -93,6 +102,11 @@ interface SandboxDirectory { */ write(paths: {[path: string]: string}): void; - /** Runs `callback` with `root` as the current directory. */ - chdir(callback: () => T): void; + /** + * Runs `callback` with `root` as the current directory, and moves + * `require.main.filename` into `root`. If `entryPoint` is set, it uses that + * as the filename within the directory, otherwise it uses the basename of the + * original `require.main.filename`. + * */ + chdir(callback: () => T, options?: {entryPoint: string}): void; } diff --git a/js-api-spec/utils.ts b/js-api-spec/utils.ts index a75da896c3..29dabfa234 100644 --- a/js-api-spec/utils.ts +++ b/js-api-spec/utils.ts @@ -12,18 +12,32 @@ export const isBrowser = !global.process; /** The name of the implementation of Sass being tested. */ export const sassImpl = info.split('\t')[0] as 'dart-sass' | 'sass-embedded'; +type Implementation = 'dart-sass' | 'sass-embedded' | 'browser'; + /** Skips the `block` of tests when running against the given `impl`. */ export function skipForImpl( - impl: 'dart-sass' | 'sass-embedded' | 'browser', + impl: Implementation | Implementation[], block: () => void ): void { - if (sassImpl === impl || (impl === 'browser' && isBrowser)) { + impl = Array.isArray(impl) ? impl : [impl]; + if (impl.includes(sassImpl) || (impl.includes('browser') && isBrowser)) { xdescribe(`[skipped for ${impl}]`, block); } else { block(); } } +export function runOnlyForImpl(impl: Implementation, block: () => void): void { + if ((impl === 'browser' && isBrowser) || impl === sassImpl) { + block(); + } else { + xdescribe( + `[skipped for ${sassImpl}${isBrowser ? ' in browser' : ''}]`, + block + ); + } +} + export const URL = isBrowser ? (global as unknown as any).URL : require('url').URL; diff --git a/lint-spec.ts b/lint-spec.ts index c6a70ee5a1..9fb8665d62 100644 --- a/lint-spec.ts +++ b/lint-spec.ts @@ -190,7 +190,7 @@ async function lintHrxSize( await hrxToRealFiles(archive); // Fixes files recursively. await lintHrxSize( - (await dir.atPath(archive.relPath())) as RealDirectory, + (await dir.atPath(p.relative(dir.path, archive.path))) as RealDirectory, reporter, {fix} ); @@ -239,7 +239,7 @@ async function hrxToRealFiles(archive: VirtualDirectory) { const subArchivePath = `${subdir.path}.hrx`; fs.writeFileSync(subArchivePath, await subdir.asArchive()); } - fs.unlinkSync(archive.path); + fs.unlinkSync(`${archive.path}.hrx`); } /** diff --git a/package-lock.json b/package-lock.json index a2131e69b0..e0236cbb5f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3867,9 +3867,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "node_modules/follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -10883,9 +10883,9 @@ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" }, "follow-redirects": { - "version": "1.15.2", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", - "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==" }, "form-data": { "version": "3.0.1", diff --git a/spec/css/plain/error/statement.hrx b/spec/css/plain/error/statement.hrx deleted file mode 100644 index a2d07dada5..0000000000 --- a/spec/css/plain/error/statement.hrx +++ /dev/null @@ -1,458 +0,0 @@ -<===> at_rule/at_root/input.scss -@import 'plain' -<===> at_rule/at_root/plain.css -a { - @at-root b { - x: y; - } -} - -<===> at_rule/at_root/error -Error: This at-rule isn't allowed in plain CSS. - , -2 | @at-root b { - | ^^^^^^^^^^^ - ' - plain.css 2:3 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> at_rule/content/input.scss -@import 'plain' -<===> at_rule/content/plain.css -@content; - -<===> at_rule/content/error -Error: This at-rule isn't allowed in plain CSS. - , -1 | @content; - | ^^^^^^^^ - ' - plain.css 1:1 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> at_rule/debug/input.scss -@import 'plain' -<===> at_rule/debug/plain.css -@debug foo; - -<===> at_rule/debug/error -Error: This at-rule isn't allowed in plain CSS. - , -1 | @debug foo; - | ^^^^^^^^^^ - ' - plain.css 1:1 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> at_rule/each/input.scss -@import 'plain' -<===> at_rule/each/plain.css -@each $i in 1 2 3 { - a { - x: y; - } -} - -<===> at_rule/each/error -Error: This at-rule isn't allowed in plain CSS. - , -1 | @each $i in 1 2 3 { - | ^^^^^^^^^^^^^^^^^^ - ' - plain.css 1:1 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> at_rule/error/input.scss -@import 'plain' -<===> at_rule/error/plain.css -@error foo; - -<===> at_rule/error/error -Error: This at-rule isn't allowed in plain CSS. - , -1 | @error foo; - | ^^^^^^^^^^ - ' - plain.css 1:1 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> at_rule/extend/input.scss -@import 'plain' -<===> at_rule/extend/plain.css -a { - @extend b; -} - -<===> at_rule/extend/error -Error: This at-rule isn't allowed in plain CSS. - , -2 | @extend b; - | ^^^^^^^^^ - ' - plain.css 2:3 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> at_rule/for/input.scss -@import 'plain' -<===> at_rule/for/plain.css -@for $i from 1 to 5 { - a { - x: y; - } -} - -<===> at_rule/for/error -Error: This at-rule isn't allowed in plain CSS. - , -1 | @for $i from 1 to 5 { - | ^^^^^^^^^^^^^^^^^^^^ - ' - plain.css 1:1 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> at_rule/function/input.scss -@import 'plain' -<===> at_rule/function/plain.css -@function foo() { - @return 1; -} - -<===> at_rule/function/error -Error: This at-rule isn't allowed in plain CSS. - , -1 | @function foo() { - | ^^^^^^^^^^^^^^^^ - ' - plain.css 1:1 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> at_rule/if/input.scss -@import 'plain' -<===> at_rule/if/plain.css -@if true { - a { - x: y; - } -} - -<===> at_rule/if/error -Error: This at-rule isn't allowed in plain CSS. - , -1 | @if true { - | ^^^^^^^^^ - ' - plain.css 1:1 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> at_rule/import/interpolated/input.scss -@import 'plain' -<===> at_rule/import/interpolated/plain.css -@import url("foo#{bar}baz"); - -<===> at_rule/import/interpolated/error -Error: Interpolation isn't allowed in plain CSS. - , -1 | @import url("foo#{bar}baz"); - | ^^^^^^ - ' - plain.css 1:17 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> at_rule/import/nested/input.scss -@import 'plain' -<===> at_rule/import/nested/plain.css -a { - @import "foo"; -} - -<===> at_rule/import/nested/output.css -a { - @import "foo"; -} - -<===> -================================================================================ -<===> at_rule/import/multi/input.scss -@import 'plain' -<===> at_rule/import/multi/plain.css -@import "foo", "bar"; - -<===> at_rule/import/multi/error -Error: expected ";". - , -1 | @import "foo", "bar"; - | ^ - ' - plain.css 1:14 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> at_rule/include/input.scss -@import 'plain' -<===> at_rule/include/plain.css -@include foo; - -<===> at_rule/include/error -Error: This at-rule isn't allowed in plain CSS. - , -1 | @include foo; - | ^^^^^^^^^^^^ - ' - plain.css 1:1 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> at_rule/mixin/input.scss -@import 'plain' -<===> at_rule/mixin/plain.css -@mixin foo { - a { - x: y; - } -} - -<===> at_rule/mixin/error -Error: This at-rule isn't allowed in plain CSS. - , -1 | @mixin foo { - | ^^^^^^^^^^^ - ' - plain.css 1:1 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> at_rule/return/input.scss -@import 'plain' -<===> at_rule/return/plain.css -@return foo; - -<===> at_rule/return/error -Error: This at-rule isn't allowed in plain CSS. - , -1 | @return foo; - | ^^^^^^^^^^^ - ' - plain.css 1:1 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> at_rule/warn/input.scss -@import 'plain' -<===> at_rule/warn/plain.css -@warn foo; - -<===> at_rule/warn/error -Error: This at-rule isn't allowed in plain CSS. - , -1 | @warn foo; - | ^^^^^^^^^ - ' - plain.css 1:1 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> at_rule/while/input.scss -@import 'plain' -<===> at_rule/while/plain.css -@while false { - a { - x: y; - } -} - -<===> at_rule/while/error -Error: This at-rule isn't allowed in plain CSS. - , -1 | @while false { - | ^^^^^^^^^^^^^ - ' - plain.css 1:1 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> at_rule/interpolation/input.scss -@import 'plain' -<===> at_rule/interpolation/plain.css -@foo a#{b}c; - -<===> at_rule/interpolation/error -Error: Interpolation isn't allowed in plain CSS. - , -1 | @foo a#{b}c; - | ^^^^ - ' - plain.css 1:7 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> style_rule/nested/input.scss -@import 'plain' -<===> style_rule/nested/plain.css -a { - b { - x: y; - } -} - -<===> style_rule/nested/error -Error: expected ":". - , -2 | b { - | ^ - ' - plain.css 2:5 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> style_rule/nested_property/input.scss -@import 'plain' -<===> style_rule/nested_property/plain.css -a { - x: { - y: z; - } -} - -<===> style_rule/nested_property/error -Error: Nested declarations aren't allowed in plain CSS. - , -2 | x: { - | ^ - ' - plain.css 2:6 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> style_rule/parent_selector/input.scss -@import 'plain' -<===> style_rule/parent_selector/plain.css -&.foo { - x: y -} - -<===> style_rule/parent_selector/error -Error: Parent selectors aren't allowed here. - , -1 | &.foo { - | ^ - ' - plain.css 1:1 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> style_rule/placeholder_selector/input.scss -@import 'plain' -<===> style_rule/placeholder_selector/plain.css -%foo { - x: y; -} - -<===> style_rule/placeholder_selector/error -Error: Placeholder selectors aren't allowed here. - , -1 | %foo { - | ^^^^ - ' - plain.css 1:1 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> style_rule/interpolation/selector/input.scss -@import 'plain' -<===> style_rule/interpolation/selector/plain.css -a#{b}c { - x: y; -} - -<===> style_rule/interpolation/selector/error -Error: Interpolation isn't allowed in plain CSS. - , -1 | a#{b}c { - | ^^^^ - ' - plain.css 1:2 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> style_rule/interpolation/declaration/input.scss -@import 'plain' -<===> style_rule/interpolation/declaration/plain.css -a { - w#{x}y: z; -} - -<===> style_rule/interpolation/declaration/error -Error: Interpolation isn't allowed in plain CSS. - , -2 | w#{x}y: z; - | ^^^^ - ' - plain.css 2:4 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> style_rule/interpolation/custom_property/input.scss -@import 'plain' -<===> style_rule/interpolation/custom_property/plain.css -a { - --b: #{c}; -} - -<===> style_rule/interpolation/custom_property/error -Error: Interpolation isn't allowed in plain CSS. - , -2 | --b: #{c}; - | ^^^^ - ' - plain.css 2:8 @import - input.scss 1:9 root stylesheet - -<===> -================================================================================ -<===> silent_comment/input.scss -@import 'plain' - -<===> silent_comment/plain.css -// silent - -<===> silent_comment/error -Error: Silent comments aren't allowed in plain CSS. - , -1 | // silent - | ^^^^^^^^^ - ' - plain.css 1:1 @import - input.scss 1:9 root stylesheet diff --git a/spec/css/plain/error/statement/at_rule.hrx b/spec/css/plain/error/statement/at_rule.hrx new file mode 100644 index 0000000000..6c0287aca2 --- /dev/null +++ b/spec/css/plain/error/statement/at_rule.hrx @@ -0,0 +1,311 @@ +<===> at_root/input.scss +@import 'plain' +<===> at_root/plain.css +a { + @at-root b { + x: y; + } +} + +<===> at_root/error +Error: This at-rule isn't allowed in plain CSS. + , +2 | @at-root b { + | ^^^^^^^^^^^ + ' + plain.css 2:3 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> content/input.scss +@import 'plain' +<===> content/plain.css +@content; + +<===> content/error +Error: This at-rule isn't allowed in plain CSS. + , +1 | @content; + | ^^^^^^^^ + ' + plain.css 1:1 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> debug/input.scss +@import 'plain' +<===> debug/plain.css +@debug foo; + +<===> debug/error +Error: This at-rule isn't allowed in plain CSS. + , +1 | @debug foo; + | ^^^^^^^^^^ + ' + plain.css 1:1 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> each/input.scss +@import 'plain' +<===> each/plain.css +@each $i in 1 2 3 { + a { + x: y; + } +} + +<===> each/error +Error: This at-rule isn't allowed in plain CSS. + , +1 | @each $i in 1 2 3 { + | ^^^^^^^^^^^^^^^^^^ + ' + plain.css 1:1 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> error/input.scss +@import 'plain' +<===> error/plain.css +@error foo; + +<===> error/error +Error: This at-rule isn't allowed in plain CSS. + , +1 | @error foo; + | ^^^^^^^^^^ + ' + plain.css 1:1 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> extend/input.scss +@import 'plain' +<===> extend/plain.css +a { + @extend b; +} + +<===> extend/error +Error: This at-rule isn't allowed in plain CSS. + , +2 | @extend b; + | ^^^^^^^^^ + ' + plain.css 2:3 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> for/input.scss +@import 'plain' +<===> for/plain.css +@for $i from 1 to 5 { + a { + x: y; + } +} + +<===> for/error +Error: This at-rule isn't allowed in plain CSS. + , +1 | @for $i from 1 to 5 { + | ^^^^^^^^^^^^^^^^^^^^ + ' + plain.css 1:1 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> function/input.scss +@import 'plain' +<===> function/plain.css +@function foo() { + @return 1; +} + +<===> function/error +Error: This at-rule isn't allowed in plain CSS. + , +1 | @function foo() { + | ^^^^^^^^^^^^^^^^ + ' + plain.css 1:1 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> if/input.scss +@import 'plain' +<===> if/plain.css +@if true { + a { + x: y; + } +} + +<===> if/error +Error: This at-rule isn't allowed in plain CSS. + , +1 | @if true { + | ^^^^^^^^^ + ' + plain.css 1:1 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> import/interpolated/input.scss +@import 'plain' +<===> import/interpolated/plain.css +@import url("foo#{bar}baz"); + +<===> import/interpolated/error +Error: Interpolation isn't allowed in plain CSS. + , +1 | @import url("foo#{bar}baz"); + | ^^^^^^ + ' + plain.css 1:17 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> import/nested/input.scss +@import 'plain' +<===> import/nested/plain.css +a { + @import "foo"; +} + +<===> import/nested/output.css +a { + @import "foo"; +} + +<===> +================================================================================ +<===> import/multi/input.scss +@import 'plain' +<===> import/multi/plain.css +@import "foo", "bar"; + +<===> import/multi/error +Error: expected ";". + , +1 | @import "foo", "bar"; + | ^ + ' + plain.css 1:14 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> include/input.scss +@import 'plain' +<===> include/plain.css +@include foo; + +<===> include/error +Error: This at-rule isn't allowed in plain CSS. + , +1 | @include foo; + | ^^^^^^^^^^^^ + ' + plain.css 1:1 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> mixin/input.scss +@import 'plain' +<===> mixin/plain.css +@mixin foo { + a { + x: y; + } +} + +<===> mixin/error +Error: This at-rule isn't allowed in plain CSS. + , +1 | @mixin foo { + | ^^^^^^^^^^^ + ' + plain.css 1:1 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> return/input.scss +@import 'plain' +<===> return/plain.css +@return foo; + +<===> return/error +Error: This at-rule isn't allowed in plain CSS. + , +1 | @return foo; + | ^^^^^^^^^^^ + ' + plain.css 1:1 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> warn/input.scss +@import 'plain' +<===> warn/plain.css +@warn foo; + +<===> warn/error +Error: This at-rule isn't allowed in plain CSS. + , +1 | @warn foo; + | ^^^^^^^^^ + ' + plain.css 1:1 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> while/input.scss +@import 'plain' +<===> while/plain.css +@while false { + a { + x: y; + } +} + +<===> while/error +Error: This at-rule isn't allowed in plain CSS. + , +1 | @while false { + | ^^^^^^^^^^^^^ + ' + plain.css 1:1 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> interpolation/input.scss +@import 'plain' +<===> interpolation/plain.css +@foo a#{b}c; + +<===> interpolation/error +Error: Interpolation isn't allowed in plain CSS. + , +1 | @foo a#{b}c; + | ^^^^ + ' + plain.css 1:7 @import + input.scss 1:9 root stylesheet diff --git a/spec/css/plain/error/expression/silent_comment.hrx b/spec/css/plain/error/statement/silent_comment.hrx similarity index 65% rename from spec/css/plain/error/expression/silent_comment.hrx rename to spec/css/plain/error/statement/silent_comment.hrx index c69d8e06a5..6c852fccaf 100644 --- a/spec/css/plain/error/expression/silent_comment.hrx +++ b/spec/css/plain/error/statement/silent_comment.hrx @@ -2,16 +2,13 @@ @import 'plain' <===> plain.css -a { - b: c // d - e; -} +// silent <===> error Error: Silent comments aren't allowed in plain CSS. , -2 | b: c // d - | ^^^^ +1 | // silent + | ^^^^^^^^^ ' - plain.css 2:8 @import + plain.css 1:1 @import input.scss 1:9 root stylesheet diff --git a/spec/css/plain/error/statement/style_rule.hrx b/spec/css/plain/error/statement/style_rule.hrx new file mode 100644 index 0000000000..e6e4815259 --- /dev/null +++ b/spec/css/plain/error/statement/style_rule.hrx @@ -0,0 +1,214 @@ +<===> nested_property/no_value/input.scss +@import 'plain' +<===> nested_property/no_value/plain.css +a { + x: { + y: z; + } +} + +<===> nested_property/no_value/error +Error: Nested declarations aren't allowed in plain CSS. + , +2 | x: { + | ^ + ' + plain.css 2:6 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> nested_property/value/input.scss +@import 'plain' + +<===> nested_property/value/plain.css +a { + b: c { + d: e; + } +} + +<===> nested_property/value/error +Error: Nested declarations aren't allowed in plain CSS. + , +2 | b: c { + | ^ + ' + plain.css 2:8 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> parent_selector/suffix/input.scss +@use 'plain'; + +<===> parent_selector/suffix/plain.css +a {&b {c: d}} + +<===> parent_selector/suffix/error +Error: Parent selectors can't have suffixes in plain CSS. + , +1 | a {&b {c: d}} + | ^^ + ' + plain.css 1:4 @use + input.scss 1:1 root stylesheet + +<===> +================================================================================ +<===> trailing_combinator/no_nesting/input.scss +@use 'plain'; + +<===> trailing_combinator/no_nesting/plain.css +a > {b: c} + +<===> trailing_combinator/no_nesting/error +Error: expected selector. + , +1 | a > {b: c} + | ^ + ' + plain.css 1:5 @use + input.scss 1:1 root stylesheet + +<===> +================================================================================ +<===> trailing_combinator/nesting/input.scss +@use 'plain'; + +<===> trailing_combinator/nesting/plain.css +a > {b {c: d}} + +<===> trailing_combinator/nesting/error +Error: expected selector. + , +1 | a > {b {c: d}} + | ^ + ' + plain.css 1:5 @use + input.scss 1:1 root stylesheet + +<===> +================================================================================ +<===> leading_combinator/top_level/input.scss +@use 'plain'; + +<===> leading_combinator/top_level/plain.css +> a {b: c} + +<===> leading_combinator/top_level/error +Error: Top-level leading combinators aren't allowed in plain CSS. + , +1 | > a {b: c} + | ^ + ' + plain.css 1:1 @use + input.scss 1:1 root stylesheet + +<===> +================================================================================ +<===> leading_combinator/through_load_css/input.scss +@use "sass:meta"; + +a {@include meta.load-css("plain")} + +<===> leading_combinator/through_load_css/plain.css +> b {c: d} + +<===> leading_combinator/through_load_css/error +Error: Top-level leading combinators aren't allowed in plain CSS. + , +1 | > b {c: d} + | ^ + ' + plain.css 1:1 load-css() + input.scss 3:4 root stylesheet + +<===> +================================================================================ +<===> leading_combinator/through_import/input.scss +a {@import "plain"} + +<===> leading_combinator/through_import/plain.css +> b {c: d} + +<===> leading_combinator/through_import/error +Error: Top-level leading combinators aren't allowed in plain CSS. + , +1 | > b {c: d} + | ^ + ' + plain.css 1:1 @import + input.scss 1:12 root stylesheet + +<===> +================================================================================ +<===> placeholder_selector/input.scss +@import 'plain' +<===> placeholder_selector/plain.css +%foo { + x: y; +} + +<===> placeholder_selector/error +Error: Placeholder selectors aren't allowed in plain CSS. + , +1 | %foo { + | ^^^^ + ' + plain.css 1:1 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> interpolation/selector/input.scss +@import 'plain' +<===> interpolation/selector/plain.css +a#{b}c { + x: y; +} + +<===> interpolation/selector/error +Error: Interpolation isn't allowed in plain CSS. + , +1 | a#{b}c { + | ^^^^ + ' + plain.css 1:2 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> interpolation/declaration/input.scss +@import 'plain' +<===> interpolation/declaration/plain.css +a { + w#{x}y: z; +} + +<===> interpolation/declaration/error +Error: Interpolation isn't allowed in plain CSS. + , +2 | w#{x}y: z; + | ^^^^ + ' + plain.css 2:4 @import + input.scss 1:9 root stylesheet + +<===> +================================================================================ +<===> interpolation/custom_property/input.scss +@import 'plain' +<===> interpolation/custom_property/plain.css +a { + --b: #{c}; +} + +<===> interpolation/custom_property/error +Error: Interpolation isn't allowed in plain CSS. + , +2 | --b: #{c}; + | ^^^^ + ' + plain.css 2:8 @import + input.scss 1:9 root stylesheet diff --git a/spec/css/plain/slash.hrx b/spec/css/plain/slash.hrx index 9d9171e268..3e8516c1a6 100644 --- a/spec/css/plain/slash.hrx +++ b/spec/css/plain/slash.hrx @@ -1,12 +1,36 @@ -<===> input.scss +<===> with_intermediate/input.scss @import "plain"; -<===> plain.css +<===> with_intermediate/plain.css +a {b: 1/2/foo/bar} + +<===> with_intermediate/output.css +a { + b: 1/2/foo/bar; +} + +<===> +================================================================================ +<===> without_intermediate/whitespace/input.scss +@import "plain"; + +<===> without_intermediate/whitespace/plain.css +a {b: 1/ / /bar} + +<===> without_intermediate/whitespace/output.css a { - slash: 1/2/foo/bar; + b: 1///bar; } -<===> output.css +<===> +================================================================================ +<===> without_intermediate/no_whitespace/input.scss +@import "plain"; + +<===> without_intermediate/no_whitespace/plain.css +a {b: 1///bar} + +<===> without_intermediate/no_whitespace/output.css a { - slash: 1/2/foo/bar; + b: 1///bar; } diff --git a/spec/css/plain/style_rule.hrx b/spec/css/plain/style_rule.hrx new file mode 100644 index 0000000000..1ae9d0e30a --- /dev/null +++ b/spec/css/plain/style_rule.hrx @@ -0,0 +1,283 @@ +<===> nesting/one_level/input.scss +@use "plain"; + +<===> nesting/one_level/plain.css +a {b {c: d}} + +<===> nesting/one_level/output.css +a { + b { + c: d; + } +} + +<===> +================================================================================ +<===> nesting/two_levels/input.scss +@use "plain"; + +<===> nesting/two_levels/plain.css +a {b {c {d: e}}} + +<===> nesting/two_levels/output.css +a { + b { + c { + d: e; + } + } +} + +<===> +================================================================================ +<===> nesting/multiple_complex/input.scss +@use "plain"; + +<===> nesting/multiple_complex/plain.css +a, b {c, d {e: f}} + +<===> nesting/multiple_complex/output.css +a, b { + c, d { + e: f; + } +} + +<===> +================================================================================ +<===> nesting/with_declaration/before/input.scss +@use "plain"; + +<===> nesting/with_declaration/before/plain.css +a { + b: c; + d {e: f} +} + +<===> nesting/with_declaration/before/output.css +a { + b: c; + d { + e: f; + } +} + +<===> +================================================================================ +<===> nesting/with_declaration/after/input.scss +@use "plain"; + +<===> nesting/with_declaration/after/plain.css +a { + b {c: d} + e: f; +} + +<===> nesting/with_declaration/after/output.css +a { + b { + c: d; + } + e: f; +} + +<===> +================================================================================ +<===> nesting/with_declaration/both/input.scss +@use "plain"; + +<===> nesting/with_declaration/both/plain.css +a { + b: c; + d {e: f} + g: h; +} + +<===> nesting/with_declaration/both/output.css +a { + b: c; + d { + e: f; + } + g: h; +} + +<===> +================================================================================ +<===> nesting/combinator/input.scss +@use "plain"; + +<===> nesting/combinator/plain.css +a {+ b {c: d}} + +<===> nesting/combinator/output.css +a { + + b { + c: d; + } +} + +<===> +================================================================================ +<===> nesting/parent/only/input.scss +@use "plain"; + +<===> nesting/parent/only/plain.css +a {& {b: c}} + +<===> nesting/parent/only/output.css +a { + & { + b: c; + } +} + +<===> +================================================================================ +<===> nesting/parent/start/input.scss +@use "plain"; + +<===> nesting/parent/start/plain.css +a {&.b {c: d}} + +<===> nesting/parent/start/output.css +a { + &.b { + c: d; + } +} + +<===> +================================================================================ +<===> nesting/parent/mid/input.scss +@use "plain"; + +<===> nesting/parent/mid/plain.css +a {.b&.c {d: e}} + +<===> nesting/parent/mid/output.css +a { + .b&.c { + d: e; + } +} + +<===> +================================================================================ +<===> nesting/parent/end/input.scss +@use "plain"; + +<===> nesting/parent/end/plain.css +a {.b& {c: d}} + +<===> nesting/parent/end/output.css +a { + .b& { + c: d; + } +} + +<===> +================================================================================ +<===> nesting/through_load_css/one_level/input.scss +@use "sass:meta"; + +a {@include meta.load-css("plain")} + +<===> nesting/through_load_css/one_level/plain.css +b {c: d} + +<===> nesting/through_load_css/one_level/output.css +a b { + c: d; +} + +<===> +================================================================================ +<===> nesting/through_load_css/two_levels/input.scss +@use "sass:meta"; + +a {@include meta.load-css("plain")} + +<===> nesting/through_load_css/two_levels/plain.css +b {c {d: e}} + +<===> nesting/through_load_css/two_levels/output.css +a b { + c { + d: e; + } +} + +<===> +================================================================================ +<===> nesting/through_load_css/top_level_parent/input.scss +@use "sass:meta"; + +a {@include meta.load-css("plain")} + +<===> nesting/through_load_css/top_level_parent/plain.css +& {b {c: d}} + +<===> nesting/through_load_css/top_level_parent/output.css +a & { + b { + c: d; + } +} + +<===> +================================================================================ +<===> nesting/through_import/one_level/input.scss +a {@import "plain"} + +<===> nesting/through_import/one_level/plain.css +b {c: d} + +<===> nesting/through_import/one_level/output.css +a b { + c: d; +} + +<===> +================================================================================ +<===> nesting/through_import/two_levels/input.scss +a {@import "plain"} + +<===> nesting/through_import/two_levels/plain.css +b {c {d: e}} + +<===> nesting/through_import/two_levels/output.css +a b { + c { + d: e; + } +} + +<===> +================================================================================ +<===> nesting/through_import/top_level_parent/input.scss +a {@import "plain"} + +<===> nesting/through_import/top_level_parent/plain.css +& {b {c: d}} + +<===> nesting/through_import/top_level_parent/output.css +a & { + b { + c: d; + } +} + +<===> +================================================================================ +<===> top_level_parent/input.scss +@use "plain"; + +<===> top_level_parent/plain.css +& {a: b} + +<===> top_level_parent/output.css +& { + a: b; +} diff --git a/spec/libsass-closed-issues/issue_1793.hrx b/spec/libsass-closed-issues/issue_1793.hrx index fdfbb80d1d..85debd0ba7 100644 --- a/spec/libsass-closed-issues/issue_1793.hrx +++ b/spec/libsass-closed-issues/issue_1793.hrx @@ -4,7 +4,7 @@ } <===> error -Error: 10px*in isn't a valid CSS value. +Error: calc(10px * 1in) isn't a valid CSS value. , 1 | @media (max-width: (2px*5in)) { | ^^^^^^^^^ diff --git a/spec/libsass-closed-issues/issue_1804/inline.hrx b/spec/libsass-closed-issues/issue_1804/inline.hrx index faefdbda43..6e5fa52700 100644 --- a/spec/libsass-closed-issues/issue_1804/inline.hrx +++ b/spec/libsass-closed-issues/issue_1804/inline.hrx @@ -4,7 +4,7 @@ foo { } <===> error -Error: 10px*in isn't a valid CSS value. +Error: calc(10px * 1in) isn't a valid CSS value. , 2 | bar: #{(2px*5in)}; | ^^^^^^^^^ diff --git a/spec/libsass-closed-issues/issue_1804/variable.hrx b/spec/libsass-closed-issues/issue_1804/variable.hrx index b5ae95fcc9..aded02337b 100644 --- a/spec/libsass-closed-issues/issue_1804/variable.hrx +++ b/spec/libsass-closed-issues/issue_1804/variable.hrx @@ -7,7 +7,7 @@ foo { } <===> error -Error: 10px*in isn't a valid CSS value. +Error: calc(10px * 1in) isn't a valid CSS value. , 5 | bar: #{($foo*$bar)}; | ^^^^^^^^^^^ diff --git a/spec/non_conformant/errors/invalid-operation/sub.hrx b/spec/non_conformant/errors/invalid-operation/sub.hrx index acd8fd7973..e5e707f994 100644 --- a/spec/non_conformant/errors/invalid-operation/sub.hrx +++ b/spec/non_conformant/errors/invalid-operation/sub.hrx @@ -3,7 +3,7 @@ test { err: 2px - 2px*2px; } <===> error -Error: 2px and 4px*px have incompatible units. +Error: 2px and calc(4px * 1px) have incompatible units. , 2 | err: 2px - 2px*2px; | ^^^^^^^^^^^^^ diff --git a/spec/operators/slash.hrx b/spec/operators/slash.hrx index 3958cc569e..715972b810 100644 --- a/spec/operators/slash.hrx +++ b/spec/operators/slash.hrx @@ -83,3 +83,13 @@ a {b: calc(1px + 1%)/calc(2px + 2%)} a { b: calc(1px + 1%)/calc(2px + 2%); } + +<===> +================================================================================ +<===> without_intermediate/whitespace/input.scss +a {b: 1/ / /bar} + +<===> without_intermediate/whitespace/output.css +a { + b: 1///bar; +} diff --git a/spec/values/calculation/acos.hrx b/spec/values/calculation/acos.hrx index 940d82d84d..8324117797 100644 --- a/spec/values/calculation/acos.hrx +++ b/spec/values/calculation/acos.hrx @@ -117,7 +117,7 @@ Error: Expected 1px to have no units. a {b: acos(-7px / 4em)} <===> error/unit/complex/error -Error: Expected -1.75px/em to have no units. +Error: Expected calc(-1.75px / 1em) to have no units. , 1 | a {b: acos(-7px / 4em)} | ^^^^^^^^^^^^^^^^ diff --git a/spec/values/calculation/asin.hrx b/spec/values/calculation/asin.hrx index 6d1b6b4fbc..1ffdf33fda 100644 --- a/spec/values/calculation/asin.hrx +++ b/spec/values/calculation/asin.hrx @@ -117,7 +117,7 @@ Error: Expected 1px to have no units. a {b: asin(-7px / 4em)} <===> error/unit/complex/error -Error: Expected -1.75px/em to have no units. +Error: Expected calc(-1.75px / 1em) to have no units. , 1 | a {b: asin(-7px / 4em)} | ^^^^^^^^^^^^^^^^ diff --git a/spec/values/calculation/atan.hrx b/spec/values/calculation/atan.hrx index 05758f8d3d..feddc873bc 100644 --- a/spec/values/calculation/atan.hrx +++ b/spec/values/calculation/atan.hrx @@ -107,7 +107,7 @@ Error: Expected 1px to have no units. a {b: atan(-7px / 4em)} <===> error/unit/complex/error -Error: Expected -1.75px/em to have no units. +Error: Expected calc(-1.75px / 1em) to have no units. , 1 | a {b: atan(-7px / 4em)} | ^^^^^^^^^^^^^^^^ diff --git a/spec/values/calculation/atan2.hrx b/spec/values/calculation/atan2.hrx index 8c96664cca..332c8b1e53 100644 --- a/spec/values/calculation/atan2.hrx +++ b/spec/values/calculation/atan2.hrx @@ -147,7 +147,7 @@ Error: 1deg and 1px are incompatible. a {b: atan2(1px*2px, 10%)} <===> error/units/complex_and_unknown/error -Error: Number 2px*px isn't compatible with CSS calculations. +Error: Number calc(2px * 1px) isn't compatible with CSS calculations. , 1 | a {b: atan2(1px*2px, 10%)} | ^^^^^^^ diff --git a/spec/values/calculation/calc/error/complex_units.hrx b/spec/values/calculation/calc/error/complex_units.hrx index 588443238d..bc4ca90084 100644 --- a/spec/values/calculation/calc/error/complex_units.hrx +++ b/spec/values/calculation/calc/error/complex_units.hrx @@ -2,7 +2,7 @@ a {b: calc(1% + 1px * 2px)} <===> multiple_numerator/within_calc/error -Error: Number 2px*px isn't compatible with CSS calculations. +Error: Number calc(2px * 1px) isn't compatible with CSS calculations. , 1 | a {b: calc(1% + 1px * 2px)} | ^^^^^^^^^^^^^^ @@ -16,7 +16,7 @@ $a: 1px * 2px; b {c: calc(1% + $a)} <===> multiple_numerator/from_variable/error -Error: Number 2px*px isn't compatible with CSS calculations. +Error: Number calc(2px * 1px) isn't compatible with CSS calculations. , 2 | b {c: calc(1% + $a)} | ^^^^^^^ @@ -29,7 +29,7 @@ Error: Number 2px*px isn't compatible with CSS calculations. a {b: calc(1% + 1 / 2px)} <===> denominator/within_calc/error -Error: Number 0.5px^-1 isn't compatible with CSS calculations. +Error: Number calc(0.5 / 1px) isn't compatible with CSS calculations. , 1 | a {b: calc(1% + 1 / 2px)} | ^^^^^^^^^^^^ @@ -44,7 +44,7 @@ $a: math.div(1, 2px); b {c: calc(1% + $a)} <===> denominator/from_variable/error -Error: Number 0.5px^-1 isn't compatible with CSS calculations. +Error: Number calc(0.5 / 1px) isn't compatible with CSS calculations. , 3 | b {c: calc(1% + $a)} | ^^^^^^^ @@ -57,7 +57,7 @@ Error: Number 0.5px^-1 isn't compatible with CSS calculations. a {b: calc(1% + 1s / 2px)} <===> numerator_and_denominator/within_calc/error -Error: Number 0.5s/px isn't compatible with CSS calculations. +Error: Number calc(0.5s / 1px) isn't compatible with CSS calculations. , 1 | a {b: calc(1% + 1s / 2px)} | ^^^^^^^^^^^^^ @@ -72,7 +72,7 @@ $a: math.div(1s, 2px); b {c: calc(1% + $a)} <===> numerator_and_denominator/from_variable/error -Error: Number 0.5s/px isn't compatible with CSS calculations. +Error: Number calc(0.5s / 1px) isn't compatible with CSS calculations. , 3 | b {c: calc(1% + $a)} | ^^^^^^^ diff --git a/spec/values/calculation/calc/error/known_incompatible/complex.hrx b/spec/values/calculation/calc/error/known_incompatible/complex.hrx index 3a6b0fabe0..eade3d04f0 100644 --- a/spec/values/calculation/calc/error/known_incompatible/complex.hrx +++ b/spec/values/calculation/calc/error/known_incompatible/complex.hrx @@ -15,7 +15,7 @@ Error: 1 and 1px are incompatible. a {b: calc(1 + 1/1px)} <===> unitless/and_denominator/error -Error: Number 1px^-1 isn't compatible with CSS calculations. +Error: Number calc(1 / 1px) isn't compatible with CSS calculations. , 1 | a {b: calc(1 + 1/1px)} | ^^^^^^^^^ @@ -28,7 +28,7 @@ Error: Number 1px^-1 isn't compatible with CSS calculations. a {b: calc(1px + 1/1px)} <===> numerator_and_denominator/error -Error: Number 1px^-1 isn't compatible with CSS calculations. +Error: Number calc(1 / 1px) isn't compatible with CSS calculations. , 1 | a {b: calc(1px + 1/1px)} | ^^^^^^^^^^^ @@ -41,7 +41,7 @@ Error: Number 1px^-1 isn't compatible with CSS calculations. a {b: calc(1px + 1px*1px)} <===> numerator_and_numerators/error -Error: Number 1px*px isn't compatible with CSS calculations. +Error: Number calc(1px * 1px) isn't compatible with CSS calculations. , 1 | a {b: calc(1px + 1px*1px)} | ^^^^^^^^^^^^^ @@ -54,7 +54,7 @@ Error: Number 1px*px isn't compatible with CSS calculations. a {b: calc(1/1px + 1/1px/1px)} <===> denominator_and_denominators/error -Error: Number 1px^-1 isn't compatible with CSS calculations. +Error: Number calc(1 / 1px) isn't compatible with CSS calculations. , 1 | a {b: calc(1/1px + 1/1px/1px)} | ^^^^^^^^^^^^^^^^^ @@ -67,7 +67,7 @@ Error: Number 1px^-1 isn't compatible with CSS calculations. a {b: calc(1px*1s + 1px*1px)} <===> mismatched_numerators/error -Error: Number 1px*s isn't compatible with CSS calculations. +Error: Number calc(1px * 1s) isn't compatible with CSS calculations. , 1 | a {b: calc(1px*1s + 1px*1px)} | ^^^^^^^^^^^^^^^^ @@ -80,7 +80,7 @@ Error: Number 1px*s isn't compatible with CSS calculations. a {b: calc(1/1px/1s + 1/1px/1px)} <===> mismatched_denominators/error -Error: Number 1(px*s)^-1 isn't compatible with CSS calculations. +Error: Number calc(1 / 1px / 1s) isn't compatible with CSS calculations. , 1 | a {b: calc(1/1px/1s + 1/1px/1px)} | ^^^^^^^^^^^^^^^^^^^^ diff --git a/spec/values/calculation/clamp.hrx b/spec/values/calculation/clamp.hrx index 0254ae43db..84a05a1b12 100644 --- a/spec/values/calculation/clamp.hrx +++ b/spec/values/calculation/clamp.hrx @@ -128,7 +128,7 @@ Error: 1px and 3s are incompatible. a {b: clamp(1px*1px, 2%*2%, 3px*3px)} <===> error/complex_unit/error -Error: Number 1px*px isn't compatible with CSS calculations. +Error: Number calc(1px * 1px) isn't compatible with CSS calculations. , 1 | a {b: clamp(1px*1px, 2%*2%, 3px*3px)} | ^^^^^^^ diff --git a/spec/values/calculation/cos.hrx b/spec/values/calculation/cos.hrx index 96524f9b0c..9c5c6fba9e 100644 --- a/spec/values/calculation/cos.hrx +++ b/spec/values/calculation/cos.hrx @@ -137,7 +137,7 @@ Error: $number: Expected 1px to have an angle unit (deg, grad, rad, turn). a {b: cos(-7px / 4em)} <===> error/unit/complex/error -Error: $number: Expected -1.75px/em to have an angle unit (deg, grad, rad, turn). +Error: $number: Expected calc(-1.75px / 1em) to have an angle unit (deg, grad, rad, turn). , 1 | a {b: cos(-7px / 4em)} | ^^^^^^^^^^^^^^^ diff --git a/spec/values/calculation/hypot.hrx b/spec/values/calculation/hypot.hrx index d885b3e855..e865f809d4 100644 --- a/spec/values/calculation/hypot.hrx +++ b/spec/values/calculation/hypot.hrx @@ -139,7 +139,7 @@ a { a {b: hypot(-7px / 4em)} <===> error/unsimplifiable/error -Error: Number -1.75px/em isn't compatible with CSS calculations. +Error: Number calc(-1.75px / 1em) isn't compatible with CSS calculations. , 1 | a {b: hypot(-7px / 4em)} | ^^^^^^^^^^ diff --git a/spec/values/calculation/log.hrx b/spec/values/calculation/log.hrx index eeab121d8b..aa680b02ba 100644 --- a/spec/values/calculation/log.hrx +++ b/spec/values/calculation/log.hrx @@ -170,7 +170,7 @@ Error: Expected 1deg to have no units. a {b: log(1px*2px, 10%)} <===> error/units/complex_and_unknown/error -Error: Expected 2px*px to have no units. +Error: Expected calc(2px * 1px) to have no units. , 1 | a {b: log(1px*2px, 10%)} | ^^^^^^^^^^^^^^^^^ diff --git a/spec/values/calculation/max.hrx b/spec/values/calculation/max.hrx index f7bd037def..cbcef327b0 100644 --- a/spec/values/calculation/max.hrx +++ b/spec/values/calculation/max.hrx @@ -103,7 +103,7 @@ Error: 1px and 2 are incompatible. a {b: max(1px*1px, 2%*2%)} <===> error/complex_unit/error -Error: Number 1px*px isn't compatible with CSS calculations. +Error: Number calc(1px * 1px) isn't compatible with CSS calculations. , 1 | a {b: max(1px*1px, 2%*2%)} | ^^^^^^^ diff --git a/spec/values/calculation/min.hrx b/spec/values/calculation/min.hrx index a1c762ff6f..9a121be89a 100644 --- a/spec/values/calculation/min.hrx +++ b/spec/values/calculation/min.hrx @@ -103,7 +103,7 @@ Error: 1px and 2 are incompatible. a {b: min(1px*1px, 2%*2%)} <===> error/complex_unit/error -Error: Number 1px*px isn't compatible with CSS calculations. +Error: Number calc(1px * 1px) isn't compatible with CSS calculations. , 1 | a {b: min(1px*1px, 2%*2%)} | ^^^^^^^ diff --git a/spec/values/calculation/mod.hrx b/spec/values/calculation/mod.hrx index 723f6847cd..8d9f107805 100644 --- a/spec/values/calculation/mod.hrx +++ b/spec/values/calculation/mod.hrx @@ -269,7 +269,7 @@ Error: 16px and 5deg are incompatible. a {b: mod(1px*2px, 10%)} <===> error/units/complex_and_unknown/error -Error: Number 2px*px isn't compatible with CSS calculations. +Error: Number calc(2px * 1px) isn't compatible with CSS calculations. , 1 | a {b: mod(1px*2px, 10%)} | ^^^^^^^ diff --git a/spec/values/calculation/rem.hrx b/spec/values/calculation/rem.hrx index f92c4afa11..f3d1c62a4f 100644 --- a/spec/values/calculation/rem.hrx +++ b/spec/values/calculation/rem.hrx @@ -269,7 +269,7 @@ Error: 16px and 5deg are incompatible. a {b: rem(1px*2px, 10%)} <===> error/units/complex_and_unknown/error -Error: Number 2px*px isn't compatible with CSS calculations. +Error: Number calc(2px * 1px) isn't compatible with CSS calculations. , 1 | a {b: rem(1px*2px, 10%)} | ^^^^^^^ diff --git a/spec/values/calculation/round/error.hrx b/spec/values/calculation/round/error.hrx index 9956371d44..b1dd43131d 100644 --- a/spec/values/calculation/round/error.hrx +++ b/spec/values/calculation/round/error.hrx @@ -114,7 +114,7 @@ Error: 10deg and 5px are incompatible. a {b: round(1px*2px, 10%)} <===> two_argument/units/complex_and_unknown/error -Error: Number 2px*px isn't compatible with CSS calculations. +Error: Number calc(2px * 1px) isn't compatible with CSS calculations. , 1 | a {b: round(1px*2px, 10%)} | ^^^^^^^ diff --git a/spec/values/calculation/sin.hrx b/spec/values/calculation/sin.hrx index 0ad4c34609..ae8f1c095a 100644 --- a/spec/values/calculation/sin.hrx +++ b/spec/values/calculation/sin.hrx @@ -147,7 +147,7 @@ Error: $number: Expected 1px to have an angle unit (deg, grad, rad, turn). a {b: sin(-7px / 4em)} <===> error/units/complex/error -Error: $number: Expected -1.75px/em to have an angle unit (deg, grad, rad, turn). +Error: $number: Expected calc(-1.75px / 1em) to have an angle unit (deg, grad, rad, turn). , 1 | a {b: sin(-7px / 4em)} | ^^^^^^^^^^^^^^^ diff --git a/spec/values/calculation/tan.hrx b/spec/values/calculation/tan.hrx index 118e2e490e..40cf126bb7 100644 --- a/spec/values/calculation/tan.hrx +++ b/spec/values/calculation/tan.hrx @@ -147,7 +147,7 @@ Error: $number: Expected 1px to have an angle unit (deg, grad, rad, turn). a {b: tan(-7px / 4em)} <===> error/units/complex/error -Error: $number: Expected -1.75px/em to have an angle unit (deg, grad, rad, turn). +Error: $number: Expected calc(-1.75px / 1em) to have an angle unit (deg, grad, rad, turn). , 1 | a {b: tan(-7px / 4em)} | ^^^^^^^^^^^^^^^ diff --git a/spec/values/numbers/degenerate.hrx b/spec/values/numbers/degenerate.hrx index 711fd4103d..69433b7729 100644 --- a/spec/values/numbers/degenerate.hrx +++ b/spec/values/numbers/degenerate.hrx @@ -69,7 +69,7 @@ a { a {b: math.div(1px * 1em, 0)} <===> error/infinity/multiple_numerator_units/error -Error: calc(Infinitypx*em) isn't a valid CSS value. +Error: calc(infinity * 1px * 1em) isn't a valid CSS value. , 2 | a {b: math.div(1px * 1em, 0)} | ^^^^^^^^^^^^^^^^^^^^^^ @@ -83,7 +83,7 @@ Error: calc(Infinitypx*em) isn't a valid CSS value. a {b: math.div(1, 0px)} <===> error/infinity/denominator_unit/error -Error: calc(Infinitypx^-1) isn't a valid CSS value. +Error: calc(infinity / 1px) isn't a valid CSS value. , 2 | a {b: math.div(1, 0px)} | ^^^^^^^^^^^^^^^^ @@ -97,7 +97,7 @@ Error: calc(Infinitypx^-1) isn't a valid CSS value. a {b: math.div(1px, 0em)} <===> error/infinity/numerator_and_denominator_unit/error -Error: calc(Infinitypx/em) isn't a valid CSS value. +Error: calc(infinity * 1px / 1em) isn't a valid CSS value. , 2 | a {b: math.div(1px, 0em)} | ^^^^^^^^^^^^^^^^^^ @@ -111,7 +111,7 @@ Error: calc(Infinitypx/em) isn't a valid CSS value. a {b: math.div(-1px * 1em, 0)} <===> error/minus_infinity/multiple_numerator_units/error -Error: calc(-Infinitypx*em) isn't a valid CSS value. +Error: calc(-infinity * 1px * 1em) isn't a valid CSS value. , 2 | a {b: math.div(-1px * 1em, 0)} | ^^^^^^^^^^^^^^^^^^^^^^^ @@ -125,7 +125,7 @@ Error: calc(-Infinitypx*em) isn't a valid CSS value. a {b: math.div(-1, 0px)} <===> error/minus_infinity/denominator_unit/error -Error: calc(-Infinitypx^-1) isn't a valid CSS value. +Error: calc(-infinity / 1px) isn't a valid CSS value. , 2 | a {b: math.div(-1, 0px)} | ^^^^^^^^^^^^^^^^^ @@ -139,7 +139,7 @@ Error: calc(-Infinitypx^-1) isn't a valid CSS value. a {b: math.div(-1px, 0em)} <===> error/minus_infinity/numerator_and_denominator_unit/error -Error: calc(-Infinitypx/em) isn't a valid CSS value. +Error: calc(-infinity * 1px / 1em) isn't a valid CSS value. , 2 | a {b: math.div(-1px, 0em)} | ^^^^^^^^^^^^^^^^^^^ @@ -153,7 +153,7 @@ Error: calc(-Infinitypx/em) isn't a valid CSS value. a {b: math.div(0px * 0em, 0)} <===> error/nan/multiple_numerator_units/error -Error: calc(NaNpx*em) isn't a valid CSS value. +Error: calc(NaN * 1px * 1em) isn't a valid CSS value. , 2 | a {b: math.div(0px * 0em, 0)} | ^^^^^^^^^^^^^^^^^^^^^^ @@ -167,7 +167,7 @@ Error: calc(NaNpx*em) isn't a valid CSS value. a {b: math.div(0, 0px)} <===> error/nan/denominator_unit/error -Error: calc(NaNpx^-1) isn't a valid CSS value. +Error: calc(NaN / 1px) isn't a valid CSS value. , 2 | a {b: math.div(0, 0px)} | ^^^^^^^^^^^^^^^^ @@ -181,7 +181,7 @@ Error: calc(NaNpx^-1) isn't a valid CSS value. a {b: math.div(0px, 0em)} <===> error/nan/numerator_and_denominator_unit/error -Error: calc(NaNpx/em) isn't a valid CSS value. +Error: calc(NaN * 1px / 1em) isn't a valid CSS value. , 2 | a {b: math.div(0px, 0em)} | ^^^^^^^^^^^^^^^^^^ diff --git a/spec/values/numbers/units/multiple/divide_by_multiple_denominators.hrx b/spec/values/numbers/units/multiple/divide_by_multiple_denominators.hrx index e74f859c1d..207b314898 100644 --- a/spec/values/numbers/units/multiple/divide_by_multiple_denominators.hrx +++ b/spec/values/numbers/units/multiple/divide_by_multiple_denominators.hrx @@ -5,7 +5,7 @@ a { <===> output.css a { - b: 1px*rad; + b: calc(1px * 1rad); } <===> warning diff --git a/spec/values/numbers/units/multiple/divide_by_multiple_numerators.hrx b/spec/values/numbers/units/multiple/divide_by_multiple_numerators.hrx index 449d2c921f..ccbc7883ae 100644 --- a/spec/values/numbers/units/multiple/divide_by_multiple_numerators.hrx +++ b/spec/values/numbers/units/multiple/divide_by_multiple_numerators.hrx @@ -5,7 +5,7 @@ a { <===> output.css a { - b: 1(px*rad)^-1; + b: calc(1 / 1px / 1rad); } <===> warning diff --git a/spec/values/numbers/units/multiple/division_cancels_both.hrx b/spec/values/numbers/units/multiple/division_cancels_both.hrx index 7f6852c0d1..463b745011 100644 --- a/spec/values/numbers/units/multiple/division_cancels_both.hrx +++ b/spec/values/numbers/units/multiple/division_cancels_both.hrx @@ -6,7 +6,7 @@ a { <===> output.css a { - b: 1rad/Hz; + b: calc(1rad / 1Hz); } <===> warning diff --git a/spec/values/numbers/units/multiple/division_cancels_compatible.hrx b/spec/values/numbers/units/multiple/division_cancels_compatible.hrx index 385a9f6a4d..4b3b6bc5fd 100644 --- a/spec/values/numbers/units/multiple/division_cancels_compatible.hrx +++ b/spec/values/numbers/units/multiple/division_cancels_compatible.hrx @@ -6,7 +6,7 @@ a { <===> output.css a { - b: 0.0104166667rad/ms*Hz; + b: calc(0.0104166667rad / 1ms / 1Hz); } <===> warning diff --git a/spec/values/numbers/units/multiple/division_cancels_denominator.hrx b/spec/values/numbers/units/multiple/division_cancels_denominator.hrx index ae0886ec23..ff5bb5d28f 100644 --- a/spec/values/numbers/units/multiple/division_cancels_denominator.hrx +++ b/spec/values/numbers/units/multiple/division_cancels_denominator.hrx @@ -6,7 +6,7 @@ a { <===> output.css a { - b: 1px*rad/Hz; + b: calc(1px * 1rad / 1Hz); } <===> warning diff --git a/spec/values/numbers/units/multiple/division_cancels_denominator_twice.hrx b/spec/values/numbers/units/multiple/division_cancels_denominator_twice.hrx index d9b240728f..9b201fc6af 100644 --- a/spec/values/numbers/units/multiple/division_cancels_denominator_twice.hrx +++ b/spec/values/numbers/units/multiple/division_cancels_denominator_twice.hrx @@ -6,7 +6,7 @@ a { <===> output.css a { - b: 1px*rad; + b: calc(1px * 1rad); } <===> warning diff --git a/spec/values/numbers/units/multiple/division_cancels_numerator.hrx b/spec/values/numbers/units/multiple/division_cancels_numerator.hrx index 0989bd8010..b35ffcf2d0 100644 --- a/spec/values/numbers/units/multiple/division_cancels_numerator.hrx +++ b/spec/values/numbers/units/multiple/division_cancels_numerator.hrx @@ -6,7 +6,7 @@ a { <===> output.css a { - b: 1rad/ms*Hz; + b: calc(1rad / 1ms / 1Hz); } <===> warning diff --git a/spec/values/numbers/units/multiple/division_cancels_numerator_twice.hrx b/spec/values/numbers/units/multiple/division_cancels_numerator_twice.hrx index a2f1b68c4a..ec7d700e94 100644 --- a/spec/values/numbers/units/multiple/division_cancels_numerator_twice.hrx +++ b/spec/values/numbers/units/multiple/division_cancels_numerator_twice.hrx @@ -6,7 +6,7 @@ a { <===> output.css a { - b: 1(ms*Hz)^-1; + b: calc(1 / 1ms / 1Hz); } <===> warning diff --git a/spec/values/numbers/units/multiple/division_cancels_unknown.hrx b/spec/values/numbers/units/multiple/division_cancels_unknown.hrx index b7054df5c3..910a8baf14 100644 --- a/spec/values/numbers/units/multiple/division_cancels_unknown.hrx +++ b/spec/values/numbers/units/multiple/division_cancels_unknown.hrx @@ -7,7 +7,7 @@ a { <===> output.css a { - b: 1bar/baz*qux; + b: calc(1bar / 1baz / 1qux); } <===> warning diff --git a/spec/values/numbers/units/multiple/multiple_denominators.hrx b/spec/values/numbers/units/multiple/multiple_denominators.hrx index b8acb91204..d0e476be60 100644 --- a/spec/values/numbers/units/multiple/multiple_denominators.hrx +++ b/spec/values/numbers/units/multiple/multiple_denominators.hrx @@ -5,7 +5,7 @@ a { <===> output.css a { - b: 1(px*rad)^-1; + b: calc(1 / 1px / 1rad); } <===> warning diff --git a/spec/values/numbers/units/multiple/multiple_numerators.hrx b/spec/values/numbers/units/multiple/multiple_numerators.hrx index 7944975a1a..125233ce55 100644 --- a/spec/values/numbers/units/multiple/multiple_numerators.hrx +++ b/spec/values/numbers/units/multiple/multiple_numerators.hrx @@ -5,5 +5,5 @@ a { <===> output.css a { - b: 1px*rad; + b: calc(1px * 1rad); } diff --git a/spec/values/numbers/units/multiple/multiplication_cancels_both.hrx b/spec/values/numbers/units/multiple/multiplication_cancels_both.hrx index 90aa07b12f..30ee9a9dd6 100644 --- a/spec/values/numbers/units/multiple/multiplication_cancels_both.hrx +++ b/spec/values/numbers/units/multiple/multiplication_cancels_both.hrx @@ -6,7 +6,7 @@ a { <===> output.css a { - b: 1rad/Hz; + b: calc(1rad / 1Hz); } <===> warning diff --git a/spec/values/numbers/units/multiple/multiplication_cancels_compatible.hrx b/spec/values/numbers/units/multiple/multiplication_cancels_compatible.hrx index fa00670e71..38266e904a 100644 --- a/spec/values/numbers/units/multiple/multiplication_cancels_compatible.hrx +++ b/spec/values/numbers/units/multiple/multiplication_cancels_compatible.hrx @@ -6,7 +6,7 @@ a { <===> output.css a { - b: 1000px*rad/Hz; + b: calc(1000px * 1rad / 1Hz); } <===> warning diff --git a/spec/values/numbers/units/multiple/multiplication_cancels_denominator.hrx b/spec/values/numbers/units/multiple/multiplication_cancels_denominator.hrx index 6e51a1e0c9..e075e321f8 100644 --- a/spec/values/numbers/units/multiple/multiplication_cancels_denominator.hrx +++ b/spec/values/numbers/units/multiple/multiplication_cancels_denominator.hrx @@ -6,7 +6,7 @@ a { <===> output.css a { - b: 1px*rad/Hz; + b: calc(1px * 1rad / 1Hz); } <===> warning diff --git a/spec/values/numbers/units/multiple/multiplication_cancels_denominator_twice.hrx b/spec/values/numbers/units/multiple/multiplication_cancels_denominator_twice.hrx index 4d9717e80d..1c471b9bbf 100644 --- a/spec/values/numbers/units/multiple/multiplication_cancels_denominator_twice.hrx +++ b/spec/values/numbers/units/multiple/multiplication_cancels_denominator_twice.hrx @@ -6,7 +6,7 @@ a { <===> output.css a { - b: 1px*rad; + b: calc(1px * 1rad); } <===> warning diff --git a/spec/values/numbers/units/multiple/multiplication_cancels_numerator.hrx b/spec/values/numbers/units/multiple/multiplication_cancels_numerator.hrx index b3682e1043..ccf7dea844 100644 --- a/spec/values/numbers/units/multiple/multiplication_cancels_numerator.hrx +++ b/spec/values/numbers/units/multiple/multiplication_cancels_numerator.hrx @@ -6,7 +6,7 @@ a { <===> output.css a { - b: 1rad/ms*Hz; + b: calc(1rad / 1ms / 1Hz); } <===> warning diff --git a/spec/values/numbers/units/multiple/multiplication_cancels_numerator_twice.hrx b/spec/values/numbers/units/multiple/multiplication_cancels_numerator_twice.hrx index 0ba910d178..78fc1da7fa 100644 --- a/spec/values/numbers/units/multiple/multiplication_cancels_numerator_twice.hrx +++ b/spec/values/numbers/units/multiple/multiplication_cancels_numerator_twice.hrx @@ -6,7 +6,7 @@ a { <===> output.css a { - b: 1(ms*Hz)^-1; + b: calc(1 / 1ms / 1Hz); } <===> warning diff --git a/spec/values/numbers/units/multiple/multiplication_cancels_unknown.hrx b/spec/values/numbers/units/multiple/multiplication_cancels_unknown.hrx index ffa12d0a9a..ab2af839f8 100644 --- a/spec/values/numbers/units/multiple/multiplication_cancels_unknown.hrx +++ b/spec/values/numbers/units/multiple/multiplication_cancels_unknown.hrx @@ -7,7 +7,7 @@ a { <===> output.css a { - b: 1foo*bar/qux; + b: calc(1foo * 1bar / 1qux); } <===> warning