New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

csstree-validator

Package Overview
Dependencies
Maintainers
1
Versions
24
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

csstree-validator - npm Package Compare versions

Comparing version 2.0.1 to 3.0.0

bin/validate.js

83

lib/cli.js

@@ -1,5 +0,19 @@

const cli = require('clap');
const reporters = require('./reporter');
const { validatePath, validateString } = require('./helpers');
import path from 'path';
import fs from 'fs';
import resolve from 'resolve';
import { command as createCommand, Error as CliError } from 'clap';
import * as reporters from './reporter/index.js';
import { validatePath, validateString } from './helpers.js';
import { version } from './version.js';
async function readStdin() {
const buffer = [];
for await (const chunk of process.stdin) {
buffer.push(chunk);
}
return buffer.join('');
}
function printResult(result, reporter) {

@@ -18,17 +32,35 @@ const output = reporter(result);

const command = cli.create('csstree-validate', '[fileOrDir]')
.version(require('../package.json').version)
.option('-r, --reporter <name>', 'Format of output: console (default), checkstyle, json, gnu', function(name) {
if (!reporters.hasOwnProperty(name)) {
throw new cli.Error('Wrong value for reporter: ' + name);
}
return name;
})
.action(function(args) {
const options = this.values;
const command = createCommand('csstree-validate [fileOrDir]')
.version(version)
.option(
'-r, --reporter <nameOrFile>',
'Output formatter: console (default), checkstyle, json, gnu or <path to a module>',
(nameOrFile) => {
const modulePath = path.resolve(process.cwd(), nameOrFile);
if (fs.existsSync(modulePath)) {
return import(modulePath);
}
if (!hasOwnProperty.call(reporters, nameOrFile)) {
try {
const resolvedPath = resolve.sync(nameOrFile, { basedir: process.cwd() });
return import(resolvedPath);
} catch (e) {}
throw new CliError('Wrong value for reporter: ' + nameOrFile);
}
return nameOrFile;
},
'console'
)
.action(async ({ options, args }) => {
const inputPath = args[0];
const reporter = reporters[options.reporter] || reporters.console;
const reporter = typeof options.reporter === 'string'
? reporters[options.reporter]
: (await options.reporter).default;
if (process.stdin.isTTY && !inputPath) {
this.showHelp();
command.run(['--help']);
return;

@@ -38,7 +70,10 @@ }

if (!inputPath) {
const buffer = [];
process.stdin
.on('data', chunk => buffer.push(chunk))
.on('end', () => printResult(validateString(buffer.join(''), '<stdin>'), reporter));
readStdin().then(input =>
printResult(validateString(input, '<stdin>'), reporter)
);
} else {
if (!fs.existsSync(inputPath)) {
throw new CliError(`ERROR! No such file or directory: ${inputPath}`);
}
printResult(validatePath(args[0]), reporter);

@@ -48,7 +83,5 @@ }

module.exports = {
run: command.run.bind(command),
isCliError(err) {
return err instanceof cli.Error;
}
};
export const run = command.run.bind(command);
export function isCliError(err) {
return err instanceof CliError;
}

@@ -1,7 +0,19 @@

const fs = require('fs');
const path = require('path');
const { validate } = require('./validate');
import { statSync, readdirSync, readFileSync } from 'fs';
import { extname, join } from 'path';
import { validate } from './validate.js';
function createResult() {
const result = Object.create(null);
result[Symbol.iterator] = function*() {
for (const [filename, errors] of Object.entries(this)) {
yield [filename, errors];
}
};
return result;
}
function defaultShouldBeValidated(filename) {
return path.extname(filename) === '.css';
return extname(filename) === '.css';
}

@@ -11,8 +23,6 @@

try {
if (fs.statSync(testPath).isDirectory()) {
return fs.readdirSync(testPath).reduce(
(result, dirFilename) =>
result.concat(collectFiles(path.join(testPath, dirFilename), shouldBeValidated)),
[]
);
if (statSync(testPath).isDirectory()) {
return [].concat(...readdirSync(testPath).map(dirFilename =>
collectFiles(join(testPath, dirFilename), shouldBeValidated)
)).sort();
} else {

@@ -26,9 +36,7 @@ return shouldBeValidated(testPath) ? [testPath] : [];

function validateDictionary(dictionary) {
const result = {};
export function validateDictionary(dictionary) {
const result = createResult();
for (const filename in dictionary) {
if (Object.prototype.hasOwnProperty.call(dictionary, filename)) {
result[filename] = validate(dictionary[filename], filename);
}
for (const filename of Object.keys(dictionary).sort()) {
result[filename] = validate(dictionary[filename], filename);
}

@@ -39,4 +47,4 @@

function validateString(css, filename) {
const result = {};
export function validateString(css, filename) {
const result = createResult();

@@ -52,8 +60,8 @@ if (!filename) {

function validateFile(filename) {
const result = {};
export function validateFile(filename) {
const result = createResult();
let css;
try {
css = fs.readFileSync(filename, 'utf-8');
css = readFileSync(filename, 'utf-8');
result[filename] = validate(css, filename);

@@ -68,3 +76,5 @@ } catch (e) {

function validateFileList(list) {
return list.reduce(function(result, filename) {
const result = createResult();
for (const filename of list) {
const res = validateFile(filename)[filename];

@@ -75,8 +85,8 @@

}
}
return result;
}, {});
return result;
}
function validatePath(searchPath, filter) {
export function validatePathList(pathList, filter) {
if (typeof filter !== 'function') {

@@ -86,6 +96,10 @@ filter = defaultShouldBeValidated;

return validateFileList(collectFiles(searchPath, filter));
const fileList = new Set([].concat(...pathList.map(path =>
collectFiles(path, filter)
)));
return validateFileList([...fileList].sort());
}
function validatePathList(pathList, filter) {
export function validatePath(searchPath, filter) {
if (typeof filter !== 'function') {

@@ -95,20 +109,3 @@ filter = defaultShouldBeValidated;

const fileList = Object.keys(
pathList.reduce(function(result, searchPath) {
collectFiles(searchPath, filter).forEach(function(filename) {
result[filename] = true;
});
return result;
}, {})
);
return validateFileList(fileList);
};
module.exports = {
validatePathList,
validatePath,
validateFile,
validateDictionary,
validateString
};
return validateFileList(collectFiles(searchPath, filter));
}

@@ -1,5 +0,6 @@

module.exports = {
...require('./helpers.js'),
...require('./validate'),
reporters: require('./reporter')
};
import * as reporters from './reporter/index.js';
export * from './version.js';
export * from './helpers.js';
export * from './validate.js';
export { reporters };

@@ -7,3 +7,3 @@ // <?xml version="1.0" encoding="utf-8"?>

// </checkstyle>
module.exports = function(data) {
export default function(result) {
const output = [

@@ -14,17 +14,15 @@ '<?xml version="1.0" encoding="utf-8"?>',

Object.keys(data).sort().forEach(function(name) {
const errors = data[name];
for (const [filename, errors] of result) {
output.push(
'\t<file name="' + name + '">',
errors.map(function(entry) {
return '\t\t<error ' +
'line="' + (entry.line || 1) + '" ' +
'column="' + (entry.column || 1) + '" ' +
'severity="error" ' +
'message="' + String(entry.message || entry.error).replace(/&/g, '&amp;').replace(/"/g, '&quot;') + '" source="csstree-validator"/>';
}).join('\n'),
'\t<file name="' + filename + '">',
errors.map(entry =>
'\t\t<error ' +
'line="' + (entry.line || 1) + '" ' +
'column="' + (entry.column || 1) + '" ' +
'severity="error" ' +
'message="' + String(entry.message || entry.error).replace(/&/g, '&amp;').replace(/"/g, '&quot;') + '" source="csstree-validator"/>'
).join('\n'),
'\t</file>'
);
});
}

@@ -34,2 +32,2 @@ output.push('</checkstyle>');

return output.join('\n');
};
}

@@ -1,9 +0,7 @@

module.exports = function(data) {
export default function(result) {
const output = [];
Object.keys(data).sort().forEach(function(filename) {
const errors = data[filename];
for (const [filename, errors] of result) {
output.push('# ' + filename);
output.push.apply(output, errors.map(function(error) {
output.push(...errors.map(function(error) {
if (error.name === 'SyntaxError') {

@@ -19,5 +17,5 @@ return ' [ERROR] ' + error.message;

output.push('');
});
}
return output.join('\n');
};
}
// "FILENAME":LINE.COLUMN: error: MESSAGE
// "FILENAME":START_LINE.COLUMN-END_LINE.COLUMN: error: MESSAGE
module.exports = function(data) {
export default function(result) {
const output = [];
Object.keys(data).sort().forEach(function(filename) {
const errors = data[filename];
output.push(errors.map(function(error) {
for (const [filename, errors] of result) {
output.push(errors.map((error) => {
const line = error.line || -1;

@@ -31,5 +29,5 @@ const column = error.column || -1;

}).join('\n'));
});
}
return output.join('\n');
};
}

@@ -1,6 +0,4 @@

module.exports = {
json: require('./json.js'),
console: require('./console.js'),
checkstyle: require('./checkstyle.js'),
gnu: require('./gnu.js')
};
export { default as json } from './json.js';
export { default as console } from './console.js';
export { default as checkstyle } from './checkstyle.js';
export { default as gnu } from './gnu.js';
// [{ "name": {file}, "line": {line},"column": {column},"message": {error} }]
module.exports = function(data) {
const output = Object.keys(data).sort().reduce(function(res, name) {
const errors = data[name];
export default function(result) {
const output = [];
return res.concat(errors.map(function(entry) {
for (const [filename, errors] of result) {
output.push(...errors.map((entry) => {
const error = entry.error || entry;
return {
name: name,
name: filename,
line: entry.line || 1,

@@ -18,5 +18,5 @@ column: entry.column || 1,

}));
}, []);
}
return JSON.stringify(output, null, 4);
};
}

@@ -1,4 +0,5 @@

const csstree = require('css-tree');
const syntax = csstree.lexer;
import { lexer, parse, walk, property as propertyName } from 'css-tree';
const syntax = lexer;
function isTargetError(error) {

@@ -18,3 +19,3 @@ if (!error) {

function validateAtrule(node) {
export function validateAtrule(node) {
const atrule = node.name;

@@ -26,4 +27,6 @@ const errors = [];

errors.push(Object.assign(error, {
atrule,
...node.loc && node.loc.start
}));
return errors;

@@ -54,3 +57,3 @@ }

function validateAtrulePrelude(atrule, prelude, preludeLoc) {
export function validateAtrulePrelude(atrule, prelude, preludeLoc) {
const errors = [];

@@ -61,6 +64,8 @@ let error;

errors.push(Object.assign(error, {
...preludeLoc || (prelude && prelude.loc && prelude.loc.start)
atrule,
...preludeLoc
}));
} else if (error = isTargetError(syntax.matchAtrulePrelude(atrule, prelude).error)) {
errors.push(Object.assign(error, {
atrule,
...error.rawMessage === 'Mismatch' &&

@@ -74,3 +79,3 @@ { details: error.message, message: 'Invalid value for `@' + atrule + '` prelude' }

function validateAtruleDescriptor(atrule, descriptor, value, descriptorLoc) {
export function validateAtruleDescriptor(atrule, descriptor, value, descriptorLoc) {
const errors = [];

@@ -83,3 +88,3 @@ let error;

descriptor,
...descriptorLoc || (value && value.loc && value.loc.start)
...descriptorLoc
}));

@@ -100,10 +105,14 @@ } else {

function validateDeclaration(property, value, valueLoc) {
export function validateDeclaration(property, value, valueLoc) {
const errors = [];
let error;
if (propertyName(property).custom) {
return errors;
}
if (error = isTargetError(syntax.checkPropertyName(property))) {
errors.push(Object.assign(error, {
property,
...valueLoc || (value && value.loc && value.loc.start)
...valueLoc
}));

@@ -121,3 +130,3 @@ } else if (error = isTargetError(syntax.matchProperty(property, value).error)) {

function validateRule(node) {
export function validateRule(node) {
const errors = [];

@@ -140,7 +149,7 @@

function validate(css, filename) {
export function validate(css, filename) {
const errors = [];
const ast = typeof css !== 'string'
? css
: csstree.parse(css, {
: parse(css, {
filename,

@@ -157,3 +166,3 @@ positions: true,

csstree.walk(ast, {
walk(ast, {
visit: 'Atrule',

@@ -165,3 +174,3 @@ enter(node) {

csstree.walk(ast, {
walk(ast, {
visit: 'Rule',

@@ -175,10 +184,1 @@ enter(node) {

};
module.exports = {
validateAtrule,
validateAtrulePrelude,
validateAtruleDescriptor,
validateRule,
validateDeclaration,
validate
};
{
"name": "csstree-validator",
"version": "2.0.1",
"version": "3.0.0",
"description": "CSS validator built on csstree",

@@ -15,34 +15,53 @@ "author": "Roman Dvornov <rdvornov@gmail.com>",

"bin": {
"csstree-validator": "./bin/validate"
"csstree-validator": "./bin/validate.js"
},
"main": "./lib/index",
"type": "module",
"main": "./cjs/index.cjs",
"exports": {
".": {
"import": "./lib/index.js",
"require": "./cjs/index.cjs"
},
"./package.json": "./package.json"
},
"unpkg": "dist/csstree-validator.esm.js",
"jsdelivr": "dist/csstree-validator.esm.js",
"browser": {
"./cjs/version.cjs": "./dist/version.cjs",
"./lib/version.js": "./dist/version.js"
},
"scripts": {
"lint-and-test": "npm run lint && npm test",
"lint": "eslint lib test",
"test": "mocha --reporter dot",
"build": "rollup --config",
"travis": "npm run lint-and-test",
"prepublishOnly": "npm run build"
"test": "mocha test --reporter ${REPORTER:-progress}",
"test:cjs": "mocha cjs-test --reporter ${REPORTER:-progress}",
"test:dist": "mocha dist/test --reporter ${REPORTER:-progress}",
"build": "npm run bundle && npm run esm-to-cjs",
"build-and-test": "npm run build && npm run test:dist && npm run test:cjs",
"bundle": "node scripts/bundle",
"bundle-and-test": "npm run bundle && npm run test:dist",
"esm-to-cjs": "node scripts/esm-to-cjs",
"esm-to-cjs-and-test": "npm run esm-to-cjs && npm run test:cjs",
"coverage": "c8 --reporter=lcovonly npm test",
"prepublishOnly": "npm run lint-and-test && npm run build-and-test"
},
"browser": {
"css-tree": "css-tree/dist/csstree.min.js"
},
"dependencies": {
"clap": "^1.1.1",
"css-tree": "^1.1.3"
"clap": "^3.0.0",
"css-tree": "^2.0.2",
"resolve": "^1.20.0"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^11.0.2",
"@rollup/plugin-json": "^4.0.2",
"@rollup/plugin-node-resolve": "^7.1.1",
"eslint": "^6.3.0",
"mocha": "^6.2.3",
"rollup": "^1.32.1",
"rollup-plugin-terser": "^5.3.0"
"c8": "^7.10.0",
"esbuild": "^0.14.2",
"eslint": "^8.4.1",
"mocha": "^9.1.3",
"rollup": "^2.60.2"
},
"engines": {
"node": ">=8.0.0"
"node": "^12.20.0 || ^14.13.0 || >=15.0.0",
"npm": ">=7.0.0"
},
"files": [
"bin",
"cjs",
"dist",

@@ -49,0 +68,0 @@ "lib"

[![NPM version](https://img.shields.io/npm/v/csstree-validator.svg)](https://www.npmjs.com/package/csstree-validator)
[![Build Status](https://travis-ci.org/csstree/validator.svg?branch=master)](https://travis-ci.org/csstree/validator)
[![Build Status](https://github.com/csstree/validator/actions/workflows/build.yml/badge.svg)](https://github.com/csstree/validator/actions/workflows/build.yml)
[![Coverage Status](https://coveralls.io/repos/github/csstree/validator/badge.svg?branch=master)](https://coveralls.io/github/csstree/validator?branch=master)
# CSS Tree Validator
# CSSTree Validator
CSS validator built on [CSSTree](https://github.com/csstree/csstree)
## How to use:
## Usage
### NPM package
```bash

@@ -16,8 +15,13 @@ > npm install csstree-validator

Manualy validate CSS string or [CSSTree's AST](https://github.com/csstree/csstree/blob/master/docs/ast.md):
Validate CSS string or [CSSTree's AST](https://github.com/csstree/csstree/blob/master/docs/ast.md):
```js
const { validate } = require('./lib');
import { validate } from 'csstree-validator';
// Commonjs:
// const { validate } = require('csstree-validator');
console.log(validate('.class { pading: 10px; border: 1px super red }', 'demo/example.css'));
const filename = 'demo/example.css';
const css = '.class { pading: 10px; border: 1px super red }';
console.log(validate(css, filename));
// [

@@ -31,35 +35,33 @@ // SyntaxError [SyntaxReferenceError]: Unknown property `pading` {

// },
// SyntaxError [SyntaxMatchError]: Mismatch {
// message: 'Invalid value for `border` property',
// rawMessage: 'Mismatch',
// syntax: '<line-width> || <line-style> || <color>',
// css: '1px super red',
// mismatchOffset: 4,
// mismatchLength: 5,
// offset: 35,
// line: 1,
// column: 36,
// loc: { source: 'demo/example.css', start: [Object], end: [Object] },
// property: 'border',
// details: 'Mismatch\n' +
// ' syntax: <line-width> || <line-style> || <color>\n' +
// ' value: 1px super red\n' +
// ' ------------^'
// }
// SyntaxError [SyntaxMatchError]: Mismatch {
// message: 'Invalid value for `border` property',
// rawMessage: 'Mismatch',
// syntax: '<line-width> || <line-style> || <color>',
// css: '1px super red',
// mismatchOffset: 4,
// mismatchLength: 5,
// offset: 35,
// line: 1,
// column: 36,
// loc: { source: 'demo/example.css', start: [Object], end: [Object] },
// property: 'border',
// details: 'Mismatch\n' +
// ' syntax: <line-width> || <line-style> || <color>\n' +
// ' value: 1px super red\n' +
// ' ------------^'
// }
// ]
```
Another option is to use helpers to validate a file or directory and buildin reporters:
Another option is to use helpers to validate a file or a directory and one of buildin reporters:
```js
const { validateFile } = require('csstree-validator');
const reporter = require('csstree-validator').reporters.checkstyle;
import { validateFile, reporters } from 'csstree-validator';
console.log(reporter(validateFile('/path/to/style.css')));
console.log(reporters.checkstyle(validateFile('./path/to/style.css')));
```
#### API
### Validate methods
Validate methods:
* validate(css, filename)
* validateAtrule(node)

@@ -70,10 +72,18 @@ * validateAtrulePrelude(atrule, prelude, preludeLoc)

* validateRule(node)
* validate(css, filename)
Helpers:
## Helpers
All helper function return an object where key is a path to a file and value is an array of errors. The result object is iterable (has `Symbol.iterator`) and can be used with `for ... of` or `...` operator.
```js
const result = validateFile('path/to/file.css');
for (const [filename, errors] of result) {
// ...
}
```
* validateString(css, filename)
* validateDictionary(dictionary)
* validateString(css, filename)
* validateFile(filename)
* validateFileList(list)
* validatePath(searchPath, filter)

@@ -84,20 +94,46 @@ * validatePathList(pathList, filter)

* json
* console
* checkstyle
* gnu
* `json`
* `console`
* `checkstyle`
* `gnu`
### Library in a browser
## Using in a browser
Available bundles to use in a browser:
- `dist/csstree-validator.js` – minified IIFE with `csstreeValidator` as a global
```html
<script src="csstree-validator/dist/csstree-validator.js"></script>
<script src="node_modules/csstree-validator/dist/csstree-validator.js"></script>
<script>
const errors = csstreeValidator.validate('.some { css: source }');
const errors = csstreeValidator.validate('.some { css: source }');
</script>
```
NOTE: Helpers and reporters are not available for browser version at the moment.
- `dist/csstree-validator.esm.js` – minified ES module
```html
<script type="module">
import { validate } from "csstree-validator/dist/csstree-validator.esm.js";
### CLI (terminal command)
const errors = validate('.some { css: source }');
</script>
```
One of CDN services like `unpkg` or `jsDelivr` can be used. By default (for short path) a ESM version is exposing. For IIFE version a full path to a bundle should be specified:
```html
<!-- ESM -->
<script type="module">
import * as csstreeValidator from 'https://cdn.jsdelivr.net/npm/csstree-validator';
import * as csstreeValidator from 'https://unpkg.com/csstree-validator';
</script>
<!-- IIFE with csstreeValidator as a global -->
<script src="https://cdn.jsdelivr.net/npm/csstree-validator/dist/csstree-validator.js"></script>
<script src="https://unpkg.com/csstree-validator/dist/csstree-validator.js"></script>
```
NOTE: Helpers and reporters are not available for browser's version.
## CLI (terminal command)
```bash

@@ -112,11 +148,43 @@ > npm install -g csstree-validator

csstree-validate [fileOrDir] [options]
csstree-validate [fileOrDir] [options]
Options:
-h, --help Output usage information
-r, --reporter <name> Format of output: console (default), checkstyle, json, gnu
-v, --version Output version
-h, --help Output usage information
-r, --reporter <nameOrFile> Output formatter: console (default), checkstyle, json, gnu
or <path to a module>
-v, --version Output version
```
### Custom reporters
In addition to predefined (buildin) reporters, you can specify the path to a module or a package with a custom reporter. Such module should export a single function which takes the validation result object and returns a string:
```js
export default function(result) {
const output = '';
for (const [filename, errors] of result) {
// ...
}
return output;
}
// For CommonJS:
// module.exports = function(result) { ... }
```
The specifier for a custom reporter might be:
- ESM module – a full path to a file with `.js` extension
- CommonJS module – a full path to a file with `.cjs` extension
- ESM package – a package name or a full path to package's module (i.e. `package/lib/index.js`)
- CommonJS package – a package name or a path to package's module (i.e. `package/lib/index.js`, `package/lib/index` or `package/lib`)
- Dual package – a package name or a full path to package's module
The resolution algorithm is testing `reporter` option value in the following order:
- If a value is a path to a file (a base dir for relative paths is `process.cwd()`), then use it a module
- If a value is a path to a package module (a base dir for `node_modules` is `process.cwd()`), then use package's module
- Otherwise the value should be a name of one of predifined reporter, or an error will be raised
## Ready to use

@@ -123,0 +191,0 @@

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc