Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,10 +91,11 @@
"property-missing-colon": "Put a colon (:) between the property and the value.\nTry: color: red",
"selector-missing": "Start every block of CSS with a selector, such as an element name or class name.\nTry: p {\n color: red;\n}",
"block-expected": "Start a block using { after your selector.\nTry: __error__ {",
"extra-tokens-after-value": "Looks like you're missing a semicolon on the line before this one.",
"extra-tokens-after-value": "The __token__ at the end of this line doesn’t belong there.",
"illegal-token-after-combinator": "After a + or > in a selector, you need to specify the name of another element, class, or ID",
"invalid-token": "This line doesn't look like valid CSS.",
"invalid-value": "__error__ isn't a meaningful value for this property. Double-check what values you can use here.",
"missing-semicolon": "Looks like you’re missing a semicolon at the end of this line.",
"require-value": "Put a value for __error__ after the colon.",
"selector-expected": "Use a comma to separate multiple tag names, classes, or IDs.",
"unknown-property": "__error__ isn't a property that CSS understands. Double-check the name of the property that you want to use."
Expand Down
16 changes: 10 additions & 6 deletions spec/assertions/validations.js
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import {assert} from 'chai';
import map from 'lodash/map';
import trim from 'lodash/trim';

function assertPassesValidation(validate, source) {
export function assertPassesValidation(validate, source) {
return assert.eventually.deepEqual(
validate(source),
[],
'source passes validation'
);
}

function assertFailsValidationWith(validate, source, ...reasons) {
export function assertFailsValidationWith(validate, source, ...reasons) {
return assert.eventually.sameMembers(
validate(source).then((errors) => map(errors, 'reason')),
reasons,
`source fails validation with reasons: ${reasons.join(', ')}`
);
}

export {
assertPassesValidation,
assertFailsValidationWith,
};
export function assertFailsValidationAtLine(validate, source, line) {
return assert.eventually.include(
validate(trim(source)).then((errors) => map(errors, 'row')),
line - 1,
`source fails validation at line: ${line}`
);
}
47 changes: 37 additions & 10 deletions spec/examples/validations/css.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,14 @@ import '../../helper';
import {
assertPassesValidation,
assertFailsValidationWith,
assertFailsValidationAtLine,
} from '../../assertions/validations';

import css from '../../../src/validations/css';

function assertPassesCssValidation(source) {
return assertPassesValidation(css, source);
}

function assertFailsCssValidationWith(source, ...errors) {
return assertFailsValidationWith(css, source, ...errors);
}

describe('css', () => {
it('allows valid flexbox', () =>
assertPassesCssValidation(`
assertPassesValidation(css, `
.flex-container {
display: flex;
flex-flow: nowrap column;
Expand All @@ -35,10 +28,44 @@ describe('css', () => {
);

it('fails with bogus flex value', () =>
assertFailsCssValidationWith(
assertFailsValidationWith(
css,
'.flex-item { flex: bogus; }',
'invalid-value'
)
);

context('missing semicolon', () => {
const stylesheet = `
p {
margin: 10px
padding: 5px;
}
`;

it('gives missing semicolon error', () =>
assertFailsValidationWith(css, stylesheet, 'missing-semicolon')
);

it('fails at the line missing the semicolon', () => {
assertFailsValidationAtLine(css, stylesheet, 1);
});
});

context('extra tokens after value', () => {
const stylesheet = `
p {
padding: 5px 5px 5px 5px 5px;
}
`;

it('gives extra tokens error', () =>
assertFailsValidationWith(css, stylesheet, 'extra-tokens-after-value')
);

it('fails at the line missing the semicolon', () =>
assertFailsValidationAtLine(css, stylesheet, 2)
);
});
});

4 changes: 2 additions & 2 deletions src/validations/Validator.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Validator {
_mapError(rawError) {
const key = this._keyForError(rawError);
if (this._errorMap.hasOwnProperty(key)) {
return this._errorMap[key](rawError);
return this._errorMap[key](rawError, this._source);
}
return null;
}
Expand All @@ -46,7 +46,7 @@ class Validator {

const location = this._locationForError(rawError);

return assign(error, location, {
return assign({}, location, error, {
text: message,
raw: message,
type: 'error',
Expand Down
28 changes: 25 additions & 3 deletions src/validations/css/prettycss.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import prettyCSS from 'PrettyCSS';
import Validator from '../Validator';
import trim from 'lodash/trim';
import startsWith from 'lodash/startsWith';
import endsWith from 'lodash/endsWith';

const RADIAL_GRADIENT_EXPR =
/^(?:(?:-(?:ms|moz|o|webkit)-)?radial-gradient|-webkit-gradient)/;
Expand All @@ -13,9 +16,28 @@ const errorMap = {
payload: {error: error.token.content},
}),

'extra-tokens-after-value': () => ({
reason: 'extra-tokens-after-value',
}),
'extra-tokens-after-value': (error, source) => {
const lineNumber = error.token.line;
const lines = source.split('\n');
const previousLine = lines[lineNumber - 2];
const thisLine = lines[lineNumber - 1];

if (
startsWith(trim(thisLine), error.token.content) &&
!endsWith(trim(previousLine), ';')
) {
return {
reason: 'missing-semicolon',
row: lineNumber - 2,
column: previousLine.length - 1,
};
}

return ({
reason: 'extra-tokens-after-value',
payload: {token: error.token.content},
});
},

'illegal-token-after-combinator': () => ({
reason: 'illegal-token-after-combinator',
Expand Down