diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..0efe6c85 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,4 @@ +# Normalize line endings to LF on checkout, on every platform. +# The test fixtures (and the stringify output they are compared against) +# assume LF, so a CRLF checkout on Windows would break the tests. +* text=auto eol=lf diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..bd0720b1 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,20 @@ +name: Test + +on: [push, pull_request] + +jobs: + test: + runs-on: ${{ matrix.os }} + strategy: + fail-fast: true + matrix: + os: [ubuntu-latest, windows-latest] + node-version: [lts/*, latest] + steps: + - uses: actions/checkout@v4 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + - run: npm ci + - run: npm test diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 7b525786..00000000 --- a/.travis.yml +++ /dev/null @@ -1,6 +0,0 @@ -arch: - - amd64 - - ppc64le -language: node_js -node_js: - - "12.18.2" diff --git a/Readme.md b/Readme.md index 0db55e44..1605d154 100644 --- a/Readme.md +++ b/Readme.md @@ -1,4 +1,14 @@ -# css [![Build Status](https://travis-ci.org/reworkcss/css.svg?branch=master)](https://travis-ci.org/reworkcss/css) +# css + +[![npm package version](https://img.shields.io/npm/v/css) ![npm downloads](https://img.shields.io/npm/dm/css)](https://www.npmjs.com/package/css) +[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/reworkcss/css/test.yml)](https://github.com/reworkcss/css/actions) +[![License](https://img.shields.io/github/license/reworkcss/css)](https://github.com/reworkcss/css) + +> [!NOTE] +> There have not been material changes to this package in 10 years. New changes and bug fixes are not planned. In the event of concrete security issues, changes may still occur. We recommend moving to something else more modern. + +> [!TIP] +> Use [@adobe/css-tools](https://github.com/adobe/css-tools) instead CSS parser / stringifier. diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..525be156 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,65 @@ +{ + "name": "css", + "version": "3.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "css", + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" + } + }, + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "license": "(MIT OR Apache-2.0)", + "bin": { + "atob": "bin/atob.js" + }, + "engines": { + "node": ">= 4.5.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-resolve": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.6.0.tgz", + "integrity": "sha512-KXBr9d/fO/bWo97NXsPIAW1bFSBOuCnjbNTBMO7N59hsv5i9yzRDfcYwwt0l04+VqnKC+EwzvJZIP/qkuMgR/w==", + "deprecated": "See https://github.com/lydell/source-map-resolve#deprecated", + "license": "MIT", + "dependencies": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0" + } + } + } +} diff --git a/package.json b/package.json index 5ac748ff..66eeded6 100644 --- a/package.json +++ b/package.json @@ -13,15 +13,8 @@ "source-map": "^0.6.1", "source-map-resolve": "^0.6.0" }, - "devDependencies": { - "mocha": "^8.0.1", - "should": "^13.2.3", - "matcha": "^0.7.0", - "bytes": "^3.1.0" - }, "scripts": { - "benchmark": "matcha", - "test": "mocha --require should --reporter spec test/*.js" + "test": "node --test" }, "author": "TJ Holowaychuk ", "license": "MIT", diff --git a/test/cases.js b/test/cases.js index 1812a2cf..57dba42f 100644 --- a/test/cases.js +++ b/test/cases.js @@ -1,5 +1,7 @@ var fs = require('fs'); var path = require('path'); +var assert = require('node:assert'); +var { describe, it } = require('node:test'); var parse = require('../').parse; var stringify = require('../').stringify; @@ -14,17 +16,17 @@ cases.forEach(function(name) { it('should match ast.json', function() { var ast = parseInput(); - ast.should.containDeep(JSON.parse(readFile(astFile))); + containDeep(ast, JSON.parse(readFile(astFile))); }); it('should match output.css', function() { var output = stringify(parseInput()); - output.should.equal(readFile(outputFile).trim()); + assert.strictEqual(output, readFile(outputFile).trim()); }); it('should match compressed.css', function() { var compressed = stringify(parseInput(), { compress: true }); - compressed.should.equal(readFile(compressedFile)); + assert.strictEqual(compressed, readFile(compressedFile)); }); function parseInput() { @@ -42,3 +44,20 @@ function readFile(file) { return src; } + +// Assert that `actual` deeply contains every property/element of `expected`. +function containDeep(actual, expected) { + if (Array.isArray(expected)) { + assert.ok(Array.isArray(actual)); + expected.forEach(function(item, i) { + containDeep(actual[i], item); + }); + } else if (expected && typeof expected === 'object') { + assert.ok(actual && typeof actual === 'object'); + Object.keys(expected).forEach(function(key) { + containDeep(actual[key], expected[key]); + }); + } else { + assert.strictEqual(actual, expected); + } +} diff --git a/test/parse.js b/test/parse.js index b39b6ee2..5dfc0774 100644 --- a/test/parse.js +++ b/test/parse.js @@ -1,5 +1,6 @@ var parse = require('../').parse; -var should = require('should'); +var assert = require('node:assert'); +var { describe, it } = require('node:test'); describe('parse(str)', function() { it('should save the filename and source', function() { @@ -8,50 +9,50 @@ describe('parse(str)', function() { source: 'booty.css' }); - ast.stylesheet.source.should.equal('booty.css'); + assert.strictEqual(ast.stylesheet.source, 'booty.css'); var position = ast.stylesheet.rules[0].position; - position.start.should.be.ok; - position.end.should.be.ok; - position.source.should.equal('booty.css'); - position.content.should.equal(css); + assert.ok(position.start); + assert.ok(position.end); + assert.strictEqual(position.source, 'booty.css'); + assert.strictEqual(position.content, css); }); it('should throw when a selector is missing', function() { - should(function() { + assert.throws(function() { parse('{size: large}'); - }).throw(); + }); - should(function() { + assert.throws(function() { parse('b { color: red; }\n{ color: green; }\na { color: blue; }'); - }).throw(); + }); }); it('should throw when a broken comment is found', function () { - should(function() { + assert.throws(function() { parse('thing { color: red; } /* b { color: blue; }'); - }).throw(); + }); - should(function() { + assert.throws(function() { parse('/*'); - }).throw(); + }); /* Nested comments should be fine */ - should(function() { + assert.doesNotThrow(function() { parse('/* /* */'); - }).not.throw(); + }); }); it('should allow empty property value', function() { - should(function() { + assert.doesNotThrow(function() { parse('p { color:; }'); - }).not.throw(); + }); }); it('should not throw with silent option', function () { - should(function() { + assert.doesNotThrow(function() { parse('thing { color: red; } /* b { color: blue; }', { silent: true }); - }).not.throw(); + }); }); it('should list the parsing errors and continue parsing', function() { @@ -61,18 +62,18 @@ describe('parse(str)', function() { }); var rules = result.stylesheet.rules; - rules.length.should.be.above(2); + assert.ok(rules.length > 2); var errors = result.stylesheet.parsingErrors; - errors.length.should.equal(2); + assert.strictEqual(errors.length, 2); - errors[0].should.have.a.property('message'); - errors[0].should.have.a.property('reason'); - errors[0].should.have.a.property('filename'); - errors[0].filename.should.equal('foo.css'); - errors[0].should.have.a.property('line'); - errors[0].should.have.a.property('column'); - errors[0].should.have.a.property('source'); + assert.ok('message' in errors[0]); + assert.ok('reason' in errors[0]); + assert.ok('filename' in errors[0]); + assert.strictEqual(errors[0].filename, 'foo.css'); + assert.ok('line' in errors[0]); + assert.ok('column' in errors[0]); + assert.ok('source' in errors[0]); }); @@ -81,28 +82,28 @@ describe('parse(str)', function() { 'thing { test: value; }\n' + '@media (min-width: 100px) { thing { test: value; } }'); - should(result.parent).equal(null); + assert.strictEqual(result.parent, null); var rules = result.stylesheet.rules; - rules.length.should.equal(2); + assert.strictEqual(rules.length, 2); var rule = rules[0]; - rule.parent.should.equal(result); - rule.declarations.length.should.equal(1); + assert.strictEqual(rule.parent, result); + assert.strictEqual(rule.declarations.length, 1); var decl = rule.declarations[0]; - decl.parent.should.equal(rule); + assert.strictEqual(decl.parent, rule); var media = rules[1]; - media.parent.should.equal(result); - media.rules.length.should.equal(1); + assert.strictEqual(media.parent, result); + assert.strictEqual(media.rules.length, 1); rule = media.rules[0]; - rule.parent.should.equal(media); + assert.strictEqual(rule.parent, media); - rule.declarations.length.should.equal(1); + assert.strictEqual(rule.declarations.length, 1); decl = rule.declarations[0]; - decl.parent.should.equal(rule); + assert.strictEqual(decl.parent, rule); }); }); diff --git a/test/stringify.js b/test/stringify.js index a31e5f23..0c232280 100644 --- a/test/stringify.js +++ b/test/stringify.js @@ -2,6 +2,8 @@ var stringify = require('../').stringify; var parse = require('../').parse; var path = require('path'); var read = require('fs').readFileSync; +var assert = require('node:assert'); +var { describe, it } = require('node:test'); var SourceMapConsumer = require('source-map').SourceMapConsumer; var SourceMapGenerator = require('source-map').SourceMapGenerator; @@ -24,29 +26,29 @@ describe('stringify(obj, {sourcemap: true})', function() { it('should generate source maps alongside when using identity compiler', function() { var result = stringify(stylesheet, { sourcemap: true }); - result.should.have.property('code'); - result.should.have.property('map'); + assert.ok('code' in result); + assert.ok('map' in result); var map = new SourceMapConsumer(result.map); - map.originalPositionFor({ line: 1, column: 0 }).should.eql(locs.tobiSelector); - map.originalPositionFor({ line: 2, column: 2 }).should.eql(locs.tobiNameName); - map.originalPositionFor({ line: 2, column: 8 }).should.eql(locs.tobiNameValue); - map.originalPositionFor({ line: 11, column: 0 }).should.eql(locs.mediaBlock); - map.originalPositionFor({ line: 12, column: 2 }).should.eql(locs.mediaOnly); - map.originalPositionFor({ line: 17, column: 0 }).should.eql(locs.comment); - map.sourceContentFor(file).should.eql(src); + assert.deepStrictEqual(map.originalPositionFor({ line: 1, column: 0 }), locs.tobiSelector); + assert.deepStrictEqual(map.originalPositionFor({ line: 2, column: 2 }), locs.tobiNameName); + assert.deepStrictEqual(map.originalPositionFor({ line: 2, column: 8 }), locs.tobiNameValue); + assert.deepStrictEqual(map.originalPositionFor({ line: 11, column: 0 }), locs.mediaBlock); + assert.deepStrictEqual(map.originalPositionFor({ line: 12, column: 2 }), locs.mediaOnly); + assert.deepStrictEqual(map.originalPositionFor({ line: 17, column: 0 }), locs.comment); + assert.deepStrictEqual(map.sourceContentFor(file), src); }); it('should generate source maps alongside when using compress compiler', function() { var result = stringify(stylesheet, { compress: true, sourcemap: true }); - result.should.have.property('code'); - result.should.have.property('map'); + assert.ok('code' in result); + assert.ok('map' in result); var map = new SourceMapConsumer(result.map); - map.originalPositionFor({ line: 1, column: 0 }).should.eql(locs.tobiSelector); - map.originalPositionFor({ line: 1, column: 5 }).should.eql(locs.tobiNameName); - map.originalPositionFor({ line: 1, column: 10 }).should.eql(locs.tobiNameValue); - map.originalPositionFor({ line: 1, column: 50 }).should.eql(locs.mediaBlock); - map.originalPositionFor({ line: 1, column: 64 }).should.eql(locs.mediaOnly); - map.sourceContentFor(file).should.eql(src); + assert.deepStrictEqual(map.originalPositionFor({ line: 1, column: 0 }), locs.tobiSelector); + assert.deepStrictEqual(map.originalPositionFor({ line: 1, column: 5 }), locs.tobiNameName); + assert.deepStrictEqual(map.originalPositionFor({ line: 1, column: 10 }), locs.tobiNameValue); + assert.deepStrictEqual(map.originalPositionFor({ line: 1, column: 50 }), locs.mediaBlock); + assert.deepStrictEqual(map.originalPositionFor({ line: 1, column: 64 }), locs.mediaOnly); + assert.deepStrictEqual(map.sourceContentFor(file), src); }); it('should apply included source maps, with paths adjusted to CWD', function() { @@ -54,18 +56,18 @@ describe('stringify(obj, {sourcemap: true})', function() { var src = read(file, 'utf8'); var stylesheet = parse(src, { source: file }); var result = stringify(stylesheet, { sourcemap: true }); - result.should.have.property('code'); - result.should.have.property('map'); + assert.ok('code' in result); + assert.ok('map' in result); var map = new SourceMapConsumer(result.map); - map.originalPositionFor({ line: 1, column: 0 }).should.eql({ + assert.deepStrictEqual(map.originalPositionFor({ line: 1, column: 0 }), { column: 0, line: 1, name: null, source: 'test/source-map/apply.scss' }); - map.originalPositionFor({ line: 2, column: 2 }).should.eql({ + assert.deepStrictEqual(map.originalPositionFor({ line: 2, column: 2 }), { column: 7, line: 1, name: null, @@ -80,7 +82,7 @@ describe('stringify(obj, {sourcemap: true})', function() { var result = stringify(stylesheet, { sourcemap: true, inputSourcemaps: false }); var map = new SourceMapConsumer(result.map); - map.originalPositionFor({ line: 1, column: 0 }).should.eql({ + assert.deepStrictEqual(map.originalPositionFor({ line: 1, column: 0 }), { column: 0, line: 1, name: null, @@ -97,7 +99,7 @@ describe('stringify(obj, {sourcemap: true})', function() { var stylesheet = parse(css, { source: src }); var result = stringify(stylesheet, { sourcemap: true }); - result.map.sources.should.eql(['/test/source.css']); + assert.deepStrictEqual(result.map.sources, ['/test/source.css']); path.sep = originalSep; }); @@ -107,7 +109,7 @@ describe('stringify(obj, {sourcemap: true})', function() { var stylesheet = parse(css); var result = stringify(stylesheet, { sourcemap: 'generator' }); - result.map.should.be.an.instanceOf(SourceMapGenerator); + assert.ok(result.map instanceof SourceMapGenerator); }); }); @@ -122,16 +124,16 @@ describe('stringify(obj, {indent: *})', function() { it('should default to two-space indent', function(){ var result = stringify(stylesheet); - result.should.eql(css.replace(/\t/g, ' ')); + assert.strictEqual(result, css.replace(/\t/g, ' ')); }); it('should indent according to the indent string', function(){ var result = stringify(stylesheet, { indent: '\t' }); - result.should.eql(css); + assert.strictEqual(result, css); }); it('should accept empty string for indent', function(){ var result = stringify(stylesheet, { indent: '' }); - result.should.eql(css.replace(/\t/g, '')); + assert.strictEqual(result, css.replace(/\t/g, '')); }); });