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 1.6.0 to 2.0.0

dist/csstree-validator.js

9

CHANGELOG.md

@@ -0,1 +1,10 @@

## 2.0.0 (November 18, 2020)
- Droped support for Nodejs < 8
- Bumped `csstree` to [1.1.1](https://github.com/csstree/csstree/releases/tag/v1.1.1)
- CLI exits with code `1` and outputs messages to `stderr` when errors (#12)
- Added built version for browsers: `dist/csstree-validator.js` (#11)
- Added at-rule validation for name, prelude and descriptor
- Added `validateAtrule`, `validateAtrulePrelude`, `validateAtruleDescriptor`, `validateRule` and `validateDeclaration` methods
## 1.6.0 (October 27, 2020)

@@ -2,0 +11,0 @@

42

lib/cli.js

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

var cli = require('clap');
var reporters = require('./reporter');
var validatePath = require('./validate.js').validatePath;
var validateString = require('./validate.js').validateString;
const cli = require('clap');
const reporters = require('./reporter');
const { validatePath, validateString } = require('./validators');
var command = cli.create('csstree-validate', '[fileOrDir]')
function printResult(result, reporter) {
const output = reporter(result);
if (Object.keys(result).length > 0) {
console.error(output);
process.exit(1);
}
if (output) {
console.log(output);
}
}
const command = cli.create('csstree-validate', '[fileOrDir]')
.version(require('../package.json').version)

@@ -15,5 +27,5 @@ .option('-r, --reporter <name>', 'Format of output: console (default), checkstyle, json, gnu', function(name) {

.action(function(args) {
var options = this.values;
var inputPath = args[0];
var reporter = reporters[options.reporter] || reporters.console;
const options = this.values;
const inputPath = args[0];
const reporter = reporters[options.reporter] || reporters.console;

@@ -26,12 +38,8 @@ if (process.stdin.isTTY && !inputPath) {

if (!inputPath) {
var buffer = [];
const buffer = [];
process.stdin
.on('data', function(chunk) {
buffer.push(chunk);
})
.on('end', function() {
console.log(reporter(validateString(buffer.join(''), '<stdin>')));
});
.on('data', chunk => buffer.push(chunk))
.on('end', () => printResult(validateString(buffer.join(''), '<stdin>'), reporter));
} else {
console.log(reporter(validatePath(args[0])));
printResult(validatePath(args[0]), reporter);
}

@@ -42,5 +50,5 @@ });

run: command.run.bind(command),
isCliError: function(err) {
isCliError(err) {
return err instanceof cli.Error;
}
};

@@ -1,9 +0,5 @@

var validators = require('./validate.js');
module.exports = {
validatePathList: validators.validatePathList,
validatePath: validators.validatePath,
validateFile: validators.validateFile,
validateString: validators.validateString,
...require('./helpers.js'),
...require('./validate'),
reporters: require('./reporter')
};

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

module.exports = function(data) {
var output = [
const output = [
'<?xml version="1.0" encoding="utf-8"?>',

@@ -15,3 +15,3 @@ '<checkstyle version="4.3">'

Object.keys(data).sort().forEach(function(name) {
var errors = data[name];
const errors = data[name];

@@ -18,0 +18,0 @@ output.push(

module.exports = function(data) {
var output = [];
const output = [];
Object.keys(data).sort().forEach(function(filename) {
var errors = data[filename];
const errors = data[filename];
output.push('# ' + filename);
output.push.apply(output, errors.map(function(entry) {
var error = entry.error || entry;
output.push.apply(output, errors.map(function(error) {
if (error.name === 'SyntaxError') {

@@ -16,4 +14,4 @@ return ' [ERROR] ' + error.message;

return ' * ' +
String(error.message)
.replace(/^[^\n]+/, entry.message)
String(error.details)
.replace(/^[^\n]+/, error.message)
.replace(/\n/g, '\n ');

@@ -20,0 +18,0 @@ }));

// "FILENAME":LINE.COLUMN: error: MESSAGE
// "FILENAME":START_LINE.COLUMN-END_LINE.COLUMN: error: MESSAGE
module.exports = function(data) {
var output = [];
const output = [];
Object.keys(data).sort().forEach(function(filename) {
var errors = data[filename];
const errors = data[filename];
output.push(errors.map(function(entry) {
var error = entry.error || entry;
var line = entry.line || -1;
var column = entry.column || -1;
var position = line + '.' + column;
var message = entry.message || entry.error.rawMessage;
var value = error.css ? ': `' + error.css + '`' : '';
var allowed = error.syntax ? '; allowed: ' + error.syntax : '';
output.push(errors.map(function(error) {
const line = error.line || -1;
const column = error.column || -1;
const message = error.message;
const value = error.css ? ': `' + error.css + '`' : '';
const allowed = error.syntax ? '; allowed: ' + error.syntax : '';
let position = line + '.' + column;
if (error.loc) {
position = error.loc.start.line + '.' +
error.loc.start.column + '-' +
position += '-' +
error.loc.end.line + '.' +

@@ -22,0 +20,0 @@ error.loc.end.column;

// [{ "name": {file}, "line": {line},"column": {column},"message": {error} }]
module.exports = function(data) {
var output = [];
const output = Object.keys(data).sort().reduce(function(res, name) {
const errors = data[name];
Object.keys(data).sort().forEach(function(name) {
var errors = data[name];
return res.concat(errors.map(function(entry) {
const error = entry.error || entry;
output = output.concat(errors.map(function(entry) {
var error = entry.error || entry;
return {

@@ -17,8 +15,8 @@ name: name,

message: entry.message,
details: error.rawMessage ? error.message : null
details: error.details || (error.rawMessage ? error.message : null)
};
}));
});
}, []);
return JSON.stringify(output, null, 4);
};

@@ -1,66 +0,48 @@

var fs = require('fs');
var path = require('path');
var csstree = require('css-tree');
var syntax = csstree.lexer;
const csstree = require('css-tree');
const syntax = csstree.lexer;
function defaultShouldBeValidated(filename) {
return path.extname(filename) === '.css';
}
function isTargetError(error) {
if (!error) {
return null;
}
function collectFiles(testPath, shouldBeValidated) {
try {
if (fs.statSync(testPath).isDirectory()) {
return fs.readdirSync(testPath).reduce(function(result, dirFilename) {
return result.concat(collectFiles(path.join(testPath, dirFilename), shouldBeValidated));
}, []);
} else {
return shouldBeValidated(testPath) ? [testPath] : [];
}
} catch (e) {
return [testPath];
if (error.name !== 'SyntaxError' &&
error.name !== 'SyntaxMatchError' &&
error.name !== 'SyntaxReferenceError') {
return null;
}
return error;
}
function validate(css, filename) {
var errors = [];
var ast = csstree.parse(css, {
filename: filename,
positions: true,
onParseError: function(error) {
errors.push(error);
}
});
function validateAtrule(node) {
const atrule = node.name;
const errors = [];
let error;
csstree.walk(ast, {
visit: 'Declaration',
enter: function(node) {
var match = syntax.matchDeclaration(node);
var error = match.error;
if (error = isTargetError(syntax.checkAtruleName(atrule))) {
errors.push(Object.assign(error, {
...node.loc && node.loc.start
}));
return errors;
}
if (error) {
var message = error.rawMessage || error.message || error;
errors.push(...validateAtrulePrelude(
atrule,
node.prelude,
(node.prelude && node.prelude.loc && node.prelude.loc.start) || (node.loc && node.loc.start)
));
// ignore errors except those which make sense
if (error.name !== 'SyntaxMatchError' &&
error.name !== 'SyntaxReferenceError') {
return;
}
if (message === 'Mismatch') {
message = 'Invalid value for `' + node.property + '`';
}
errors.push({
name: error.name,
node: node,
loc: error.loc || node.loc,
line: error.line || node.loc && node.loc.start && node.loc.start.line,
column: error.column || node.loc && node.loc.start && node.loc.start.column,
property: node.property,
message: message,
error: error
});
if (node.block && node.block.children) {
node.block.children.forEach(child => {
if (child.type === 'Declaration') {
errors.push(...validateAtruleDescriptor(
atrule,
child.property,
child.value,
child.loc && child.loc.start
));
}
}
});
});
}

@@ -70,83 +52,122 @@ return errors;

function validateDictionary(dictionary) {
var result = {};
function validateAtrulePrelude(atrule, prelude, preludeLoc) {
const errors = [];
let error;
for (var filename in dictionary) {
if (Object.prototype.hasOwnProperty.call(dictionary, filename)) {
result[filename] = validate(dictionary[filename], filename);
}
if (error = isTargetError(syntax.checkAtrulePrelude(atrule, prelude))) {
errors.push(Object.assign(error, {
...preludeLoc || (prelude && prelude.loc && prelude.loc.start)
}));
} else if (error = isTargetError(syntax.matchAtrulePrelude(atrule, prelude).error)) {
errors.push(Object.assign(error, {
...error.rawMessage === 'Mismatch' &&
{ details: error.message, message: 'Invalid value for `@' + atrule + '` prelude' }
}));
}
return result;
return errors;
}
function validateString(css, filename) {
var result = {};
function validateAtruleDescriptor(atrule, descriptor, value, descriptorLoc) {
const errors = [];
let error;
if (!filename) {
filename = '<unknown>';
if (error = isTargetError(syntax.checkAtruleDescriptorName(atrule, descriptor))) {
errors.push(Object.assign(error, {
atrule,
descriptor,
...descriptorLoc || (value && value.loc && value.loc.start)
}));
} else {
if (error = isTargetError(syntax.matchAtruleDescriptor(atrule, descriptor, value).error)) {
errors.push(Object.assign(error, {
atrule,
descriptor,
...error.rawMessage === 'Mismatch' &&
{ details: error.message, message: 'Invalid value for `' + descriptor + '` descriptor' }
}));
}
}
result[filename] = validate(css, filename);
return result;
return errors;
}
function validateFile(filename) {
var result = {};
var css;
function validateDeclaration(property, value, valueLoc) {
const errors = [];
let error;
try {
css = fs.readFileSync(filename, 'utf-8');
result[filename] = validate(css, filename);
} catch (e) {
result[filename] = [e];
if (error = isTargetError(syntax.checkPropertyName(property))) {
errors.push(Object.assign(error, {
property,
...valueLoc || (value && value.loc && value.loc.start)
}));
} else if (error = isTargetError(syntax.matchProperty(property, value).error)) {
errors.push(Object.assign(error, {
property,
...error.rawMessage === 'Mismatch' &&
{ details: error.message, message: 'Invalid value for `' + property + '` property' }
}));
}
return result;
return errors;
}
function validateFileList(list) {
return list.reduce(function(result, filename) {
var res = validateFile(filename)[filename];
function validateRule(node) {
const errors = [];
if (res && res.length !== 0) {
result[filename] = res;
}
if (node.block && node.block.children) {
node.block.children.forEach(child => {
if (child.type === 'Declaration') {
errors.push(...validateDeclaration(
child.property,
child.value,
child.loc && child.loc.start
));
}
});
}
return result;
}, {});
return errors;
}
function validatePath(searchPath, shouldBeValidated) {
if (typeof shouldBeValidated !== 'function') {
shouldBeValidated = defaultShouldBeValidated;
}
function validate(css, filename) {
const errors = [];
const ast = typeof css !== 'string'
? css
: csstree.parse(css, {
filename,
positions: true,
parseAtrulePrelude: false,
parseRulePrelude: false,
parseValue: false,
parseCustomProperty: false,
onParseError(error) {
errors.push(error);
}
});
return validateFileList(collectFiles(searchPath, shouldBeValidated));
}
csstree.walk(ast, {
visit: 'Atrule',
enter(node) {
errors.push(...validateAtrule(node));
}
});
function validatePathList(pathList, shouldBeValidated) {
if (typeof shouldBeValidated !== 'function') {
shouldBeValidated = defaultShouldBeValidated;
}
csstree.walk(ast, {
visit: 'Rule',
enter(node) {
errors.push(...validateRule(node));
}
});
var fileList = Object.keys(
pathList.reduce(function(result, searchPath) {
collectFiles(searchPath, shouldBeValidated).forEach(function(filename) {
result[filename] = true;
});
return result;
}, {})
);
return validateFileList(fileList);
return errors;
};
module.exports = {
validatePathList: validatePathList,
validatePath: validatePath,
validateFile: validateFile,
validateDictionary: validateDictionary,
validateString: validateString
validateAtrule,
validateAtrulePrelude,
validateAtruleDescriptor,
validateRule,
validateDeclaration,
validate
};
{
"name": "csstree-validator",
"version": "1.6.0",
"description": "CSS validator build on csstree",
"version": "2.0.0",
"description": "CSS validator built on csstree",
"author": "Roman Dvornov <rdvornov@gmail.com>",

@@ -22,19 +22,30 @@ "license": "MIT",

"test": "mocha --reporter dot",
"travis": "npm run lint-and-test"
"build": "rollup --config",
"travis": "npm run lint-and-test",
"prepublishOnly": "npm run build"
},
"browser": {
"css-tree": "css-tree/dist/csstree.min.js"
},
"dependencies": {
"clap": "^1.1.1",
"css-tree": "^1.0.0"
"css-tree": "^1.1.1"
},
"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": "^5.2.0"
"mocha": "^6.2.3",
"rollup": "^1.32.1",
"rollup-plugin-terser": "^5.3.0"
},
"engines": {
"node": ">=0.10.0"
"node": ">=8.0.0"
},
"files": [
"bin",
"dist",
"lib"
]
}

@@ -6,6 +6,86 @@ [![NPM version](https://img.shields.io/npm/v/csstree-validator.svg)](https://www.npmjs.com/package/csstree-validator)

CSS validator built on [CSSTree](https://github.com/csstree/csstree)
## How to use:
### NPM package
```bash
> npm i -g csstree-validator
> npm install csstree-validator
```
Manualy validate CSS string or [CSSTree's AST](https://github.com/csstree/csstree/blob/master/docs/ast.md):
```js
const { validate } = require('./lib');
console.log(validate('.class { pading: 10px; border: 1px super red }', 'demo/example.css'));
// [
// SyntaxError [SyntaxReferenceError]: Unknown property `pading` {
// reference: 'pading',
// property: 'pading',
// offset: 9,
// line: 1,
// column: 10
// },
// 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:
```js
const { validateFile } = require('csstree-validator');
const reporter = require('csstree-validator').reporters.checkstyle;
console.log(reporter(validateFile('/path/to/style.css')));
```
#### API
Validate methods:
* validateAtrule(node)
* validateAtrulePrelude(atrule, prelude, preludeLoc)
* validateAtruleDescriptor(atrule, descriptor, value, descriptorLoc)
* validateDeclaration(property, value, valueLoc)
* validateRule(node)
* validate(css, filename)
Helpers:
* validateDictionary(dictionary)
* validateString(css, filename)
* validateFile(filename)
* validateFileList(list)
* validatePath(searchPath, filter)
* validatePathList(pathList, filter)
Reporters
* json
* console
* checkstyle
* gnu
### CLI (terminal command)
```bash
> npm install -g csstree-validator
> csstree-validator /path/to/style.css

@@ -27,14 +107,5 @@ ```

## API
```js
var validateFile = require('csstree-validator').validateFile;
var reporter = require('csstree-validator').reporters.checkstyle;
console.log(reporter(validateFile('/path/to/style.css')));
```
## Ready to use
Some plugins that are using `csstree-validator`:
Plugins that are using `csstree-validator`:

@@ -41,0 +112,0 @@ * [Sublime plugin](https://github.com/csstree/SublimeLinter-contrib-csstree)

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