Skip to content

Commit

Permalink
Refactor validate, added atrule validation
Browse files Browse the repository at this point in the history
  • Loading branch information
lahmatiy committed Nov 18, 2020
1 parent 82f1389 commit 79eba95
Show file tree
Hide file tree
Showing 16 changed files with 462 additions and 265 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
- Droped support for Nodejs < 8
- CLI exits with code `1` and outputs 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)

Expand Down
15 changes: 2 additions & 13 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
const {
validatePathList,
validatePath,
validateFile,
validateDictionary,
validateString
} = require('./validators.js');

module.exports = {
validatePathList,
validatePath,
validateFile,
validateDictionary,
validateString,
...require('./validators.js'),
...require('./validate'),
reporters: require('./reporter')
};
8 changes: 3 additions & 5 deletions lib/reporter/console.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,14 @@ module.exports = function(data) {
const errors = data[filename];

output.push('# ' + filename);
output.push.apply(output, errors.map(function(entry) {
const error = entry.error || entry;

output.push.apply(output, errors.map(function(error) {
if (error.name === 'SyntaxError') {
return ' [ERROR] ' + error.message;
}

return ' * ' +
String(error.message)
.replace(/^[^\n]+/, entry.message)
String(error.details)
.replace(/^[^\n]+/, error.message)
.replace(/\n/g, '\n ');
}));
output.push('');
Expand Down
12 changes: 5 additions & 7 deletions lib/reporter/gnu.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,16 @@ module.exports = function(data) {
Object.keys(data).sort().forEach(function(filename) {
const errors = data[filename];

output.push(errors.map(function(entry) {
const error = entry.error || entry;
const line = entry.line || -1;
const column = entry.column || -1;
const message = entry.message || entry.error.rawMessage;
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 + '.' +
error.loc.end.column;
}
Expand Down
2 changes: 1 addition & 1 deletion lib/reporter/json.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module.exports = function(data) {
column: entry.column || 1,
property: entry.property,
message: entry.message,
details: error.rawMessage ? error.message : null
details: error.details || (error.rawMessage ? error.message : null)
};
}));
}, []);
Expand Down
191 changes: 158 additions & 33 deletions lib/validate.js
Original file line number Diff line number Diff line change
@@ -1,47 +1,172 @@
const csstree = require('css-tree');
const syntax = csstree.lexer;

module.exports = function validate(css, filename) {
function isTargetError(error) {
if (!error) {
return null;
}

if (error.name !== 'SyntaxError' &&
error.name !== 'SyntaxMatchError' &&
error.name !== 'SyntaxReferenceError') {
return null;
}

return error;
}

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

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' }
}));
}
}

return errors;
}

function validateAtrule(node) {
const atrule = node.name;
const errors = [];
let error;

if (error = isTargetError(syntax.checkAtruleName(atrule))) {
errors.push(Object.assign(error, {
...node.loc && node.loc.start
}));
return errors;
}

errors.push(...validateAtrulePrelude(
atrule,
node.prelude,
(node.prelude && node.prelude.loc && node.prelude.loc.start) || (node.loc && node.loc.start)
));

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
));
}
});
}

return errors;
}

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

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 errors;
}

function validateDeclaration(property, value, valueLoc) {
const errors = [];
let error;

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 errors;
}

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

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 errors;
}

function validate(css, filename) {
const errors = [];
const ast = csstree.parse(css, {
filename,
positions: true,
onParseError(error) {
errors.push(error);
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);
}
});

csstree.walk(ast, {
visit: 'Atrule',
enter(node) {
errors.push(...validateAtrule(node));
}
});

csstree.walk(ast, {
visit: 'Declaration',
visit: 'Rule',
enter(node) {
const { error } = syntax.matchDeclaration(node);

if (error) {
let message = error.rawMessage || error.message || error;

// 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,
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,
error
});
}
errors.push(...validateRule(node));
}
});

return errors;
};

module.exports = {
validateAtrule,
validateAtrulePrelude,
validateAtruleDescriptor,
validateRule,
validateDeclaration,
validate
};
2 changes: 1 addition & 1 deletion lib/validators.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const fs = require('fs');
const path = require('path');
const validate = require('./validate');
const { validate } = require('./validate');

function defaultShouldBeValidated(filename) {
return path.extname(filename) === '.css';
Expand Down
14 changes: 7 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
},
"dependencies": {
"clap": "^1.1.1",
"css-tree": "^1.0.0"
"css-tree": "^1.1.1"
},
"devDependencies": {
"@rollup/plugin-commonjs": "^11.0.2",
Expand Down
6 changes: 3 additions & 3 deletions test/fixture/reporter/checkstyle
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<checkstyle version="4.3">
<file name="match.css">
<error line="1" column="16" severity="error" message="Invalid value for `color`" source="csstree-validator"/>
<error line="1" column="33" severity="error" message="Invalid value for `border`" source="csstree-validator"/>
<error line="1" column="16" severity="error" message="Invalid value for `color` property" source="csstree-validator"/>
<error line="1" column="33" severity="error" message="Invalid value for `border` property" source="csstree-validator"/>
<error line="1" column="46" severity="error" message="Unknown property `unknown`" source="csstree-validator"/>
</file>
<file name="parse.css">
<error line="1" column="11" severity="error" message="Colon is expected" source="csstree-validator"/>
<error line="1" column="32" severity="error" message="Invalid value for `color`" source="csstree-validator"/>
<error line="1" column="32" severity="error" message="Invalid value for `color` property" source="csstree-validator"/>
</file>
</checkstyle>
6 changes: 3 additions & 3 deletions test/fixture/reporter/console
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
# match.css
* Invalid value for `color`
* Invalid value for `color` property
syntax: <color>
value: 123
--------^
* Invalid value for `border`
* Invalid value for `border` property
syntax: <line-width> || <line-style> || <color>
value: 1px unknown red
------------^
* Unknown property `unknown`

# parse.css
[ERROR] Colon is expected
* Invalid value for `color`
* Invalid value for `color` property
syntax: <color>
value: red green
------------^
6 changes: 3 additions & 3 deletions test/fixture/reporter/gnu
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"match.css":1.16-1.19: error: Invalid value for `color`: `123`; allowed: <color>
"match.css":1.33-1.40: error: Invalid value for `border`: `1px unknown red`; allowed: <line-width> || <line-style> || <color>
"match.css":1.16-1.19: error: Invalid value for `color` property: `123`; allowed: <color>
"match.css":1.33-1.40: error: Invalid value for `border` property: `1px unknown red`; allowed: <line-width> || <line-style> || <color>
"match.css":1.46: error: Unknown property `unknown`
"parse.css":1.11: error: Colon is expected
"parse.css":1.32-1.37: error: Invalid value for `color`: `red green`; allowed: <color>
"parse.css":1.32-1.37: error: Invalid value for `color` property: `red green`; allowed: <color>
Loading

0 comments on commit 79eba95

Please sign in to comment.