├── .gitignore
├── test
├── test-files
│ ├── valid.css
│ ├── invalid.css
│ ├── fake-jigsaw
│ │ ├── GET_%2Fcss-validator%2Fvalidator%3Fo_319e22637d6dc24aa73a46c650902961.json
│ │ ├── POST_%2Fcss-validator%2Fvalidator_526485bb7d0707a4b57a91373385e04e.json
│ │ ├── POST_%2Fcss-validator%2Fvalidator_aa857c07950aabae1aa1f97d95880a5e.json
│ │ ├── POST_%2Fcss-validator%2Fvalidator_d0fc59552c514a6cfde2b979f258009e.json
│ │ ├── POST_%2Fcss-validator%2Fvalidator_e635bbc34c4e4b6f66663de649b058e5.json
│ │ ├── GET_%2Fcss-validator%2Fvalidator%3Fo_866e5724035789665269ec61dc1641a4.json
│ │ ├── POST_%2Fcss-validator%2Fvalidator_70e94cf96374050f7556586668bf6184.json
│ │ ├── POST_%2Fcss-validator%2Fvalidator_d56efd3da2bc6ea5b0c8346843f619dc.json
│ │ ├── POST_%2Fcss-validator%2Fvalidator_b01b6dda79fbb8a05a4313bd36d18166.json
│ │ └── POST_%2Fcss-validator%2Fvalidator_bac997c6520131945d11659def6876b3.json
│ └── invalid.xml
├── utils
│ └── fake-jigsaw.js
├── cli_test.js
└── css-validator_test.js
├── bin
└── css-validator
├── docs
└── examples.js
├── .travis.yml
├── .jshintrc
├── lib
├── css-validator.js
├── xml-parser.js
├── cli.js
└── validation-stream.js
├── CHANGELOG.md
├── UNLICENSE
├── package.json
├── .jscsrc
└── README.md
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | yarn.lock
3 |
--------------------------------------------------------------------------------
/test/test-files/valid.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: url(abcd);
3 | }
4 |
--------------------------------------------------------------------------------
/test/test-files/invalid.css:
--------------------------------------------------------------------------------
1 | body {
2 | background: url(ab'cd');
3 | -moz-box-sizing: content-box;
4 | }
5 |
6 |
--------------------------------------------------------------------------------
/bin/css-validator:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | // Load in our dependencies
3 | var cli = require('../lib/cli');
4 |
5 | // Parse our arguments
6 | cli.parse(process.argv);
7 |
--------------------------------------------------------------------------------
/docs/examples.js:
--------------------------------------------------------------------------------
1 | var cssValidate = require('../');
2 | var css = [
3 | "body {",
4 | " background: url(ab'cd');",
5 | " -moz-box-sizing: content-box;",
6 | "}",
7 | ].join('\n');
8 |
9 | cssValidate(css, function (err, data) {
10 | console.log(data);
11 | });
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 | language: node_js
3 | node_js:
4 | - "14"
5 | - "12"
6 |
7 | before_install:
8 | - curl --location http://rawgit.com/twolfson/fix-travis-ci/master/lib/install.sh | bash -s
9 |
10 | notifications:
11 | email:
12 | recipients:
13 | - todd@twolfson.com
14 | on_success: change
15 | on_failure: change
16 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "eqeqeq": true,
3 | "freeze": true,
4 | "immed": true,
5 | "latedef": true,
6 | "nonbsp": true,
7 | "undef": true,
8 | "strict": false,
9 | "node": true,
10 | "sub": false,
11 | "globals": {
12 | "exports": true,
13 | "describe": true,
14 | "before": true,
15 | "beforeEach": true,
16 | "after": true,
17 | "afterEach": true,
18 | "it": true
19 | },
20 | "curly": true,
21 | "indent": 2,
22 | "newcap": true,
23 | "noarg": true,
24 | "quotmark": "single",
25 | "unused": "vars",
26 | "maxparams": 4,
27 | "maxdepth": 5
28 | }
29 |
--------------------------------------------------------------------------------
/lib/css-validator.js:
--------------------------------------------------------------------------------
1 | var ValidationStream = require('./validation-stream');
2 |
3 | // Define our CSS validator
4 | function validateCss(options, cb) {
5 | // Create an emitter
6 | var validator = new ValidationStream(options);
7 |
8 | if (cb) {
9 | // Callback with any errors
10 | validator.on('error', cb);
11 |
12 | // Save the result when emitted
13 | var result;
14 | validator.on('data', function (_result) {
15 | result = _result;
16 | });
17 |
18 | // Callback when done
19 | validator.on('end', function () {
20 | cb(null, result);
21 | });
22 | } else {
23 | return validator;
24 | }
25 | }
26 |
27 | // Export validateCss
28 | module.exports = validateCss;
29 |
--------------------------------------------------------------------------------
/test/utils/fake-jigsaw.js:
--------------------------------------------------------------------------------
1 | // Load in our dependencies
2 | var express = require('express');
3 | var eightTrack = require('eight-track');
4 | var normalizeMultipart = require('eight-track-normalize-multipart');
5 |
6 | // Define our helpers
7 | exports.w3cUrl = 'http://localhost:1337/css-validator/validator';
8 | exports.run = function (options) {
9 | before(function () {
10 | this.fakeJigsaw = express().use(eightTrack({
11 | url: 'http://jigsaw.w3.org',
12 | fixtureDir: __dirname + '/../test-files/fake-jigsaw/',
13 | normalizeFn: options.multipart ? normalizeMultipart : null
14 | })).listen(1337);
15 | });
16 | after(function (done) {
17 | this.fakeJigsaw.close(done);
18 | delete this.fakeJigsaw;
19 | });
20 | };
21 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # css-validator changelog
2 | 0.11.0 - Added HTTPS support via @allthegoodusernamesaregone in #19
3 |
4 | 0.10.0 - Upgraded dependencies, fixed delay, and updated fixtures via @antongolub in #18
5 |
6 | 0.9.0 - Added more CLI validation options via @antongolub in #17
7 |
8 | 0.8.1 - Upgraded to express@3.21.2 to fix GitHub vulnerability warning
9 |
10 | 0.8.0 - Added empty CSS support via @niedzielski in #7 and fixed URI support
11 |
12 | 0.7.0 - Added CLI via @niedzielski in #5
13 |
14 | 0.6.0 - Moved from Grunt linting to `twolfson-style`
15 |
16 | 0.5.3 - Repaired mistakes in README. Fixes #3 and #4
17 |
18 | 0.5.2 - Added `foundry` for consitent releases
19 |
20 | 0.5.1 - Moved to `eight-track` for HTTP fixtures over `nock`
21 |
22 | 0.5.0 - Broke up code further, moved XmlParser and ValidationStream to Duplex, and emitting result as `data` event
23 |
24 | 0.4.0 - Implemented CssValidator as a Writable stream
25 |
26 | 0.3.0 - Moved from GET to POST for requests. Fixes #1
27 |
28 | 0.2.0 - Added ability to get event emitter
29 |
30 | 0.1.1 - Added missing error callback
31 |
32 | 0.1.0 - Initial release
33 |
--------------------------------------------------------------------------------
/UNLICENSE:
--------------------------------------------------------------------------------
1 | This is free and unencumbered software released into the public domain.
2 |
3 | Anyone is free to copy, modify, publish, use, compile, sell, or
4 | distribute this software, either in source code form or as a compiled
5 | binary, for any purpose, commercial or non-commercial, and by any
6 | means.
7 |
8 | In jurisdictions that recognize copyright laws, the author or authors
9 | of this software dedicate any and all copyright interest in the
10 | software to the public domain. We make this dedication for the benefit
11 | of the public at large and to the detriment of our heirs and
12 | successors. We intend this dedication to be an overt act of
13 | relinquishment in perpetuity of all present and future rights to this
14 | software under copyright law.
15 |
16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
24 | For more information, please refer to
25 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "css-validator",
3 | "description": "Validate CSS via W3C's service",
4 | "version": "0.11.0",
5 | "homepage": "https://github.com/twolfson/css-validator",
6 | "author": {
7 | "name": "Todd Wolfson",
8 | "email": "todd@twolfson.com",
9 | "url": "http://twolfson.com/"
10 | },
11 | "repository": {
12 | "type": "git",
13 | "url": "git://github.com/twolfson/css-validator.git"
14 | },
15 | "bugs": {
16 | "url": "https://github.com/twolfson/css-validator/issues"
17 | },
18 | "licenses": [
19 | {
20 | "type": "UNLICENSE",
21 | "url": "https://github.com/twolfson/css-validator/blob/master/UNLICENSE"
22 | }
23 | ],
24 | "main": "lib/css-validator",
25 | "engines": {
26 | "node": ">= 4.0.0"
27 | },
28 | "bin": {
29 | "css-validator": "bin/css-validator"
30 | },
31 | "scripts": {
32 | "lint": "twolfson-style lint bin/ lib/ test/",
33 | "test": "mocha && npm run lint"
34 | },
35 | "dependencies": {
36 | "async": "~3.2.0",
37 | "commander": "~7.1.0",
38 | "form-data": "~4.0.0",
39 | "obj-extend": "~0.1.0",
40 | "readable-stream": "~3.6.0",
41 | "sax": "~1.2.4"
42 | },
43 | "devDependencies": {
44 | "chai": "~4.3.3",
45 | "eight-track": "~2.1.0",
46 | "eight-track-normalize-multipart": "~0.2.0",
47 | "express": "~4.17.1",
48 | "foundry": "~4.4.2",
49 | "foundry-release-git": "~2.0.3",
50 | "foundry-release-npm": "~2.0.2",
51 | "jscs": "~3.0.7",
52 | "jshint": "~2.12.0",
53 | "mocha": "~8.3.1",
54 | "twolfson-style": "~1.6.1"
55 | },
56 | "keywords": [
57 | "css",
58 | "validate",
59 | "w3c"
60 | ],
61 | "foundry": {
62 | "releaseCommands": [
63 | "foundry-release-git",
64 | "foundry-release-npm"
65 | ]
66 | }
67 | }
--------------------------------------------------------------------------------
/test/cli_test.js:
--------------------------------------------------------------------------------
1 | // Load in our dependencies
2 | var expect = require('chai').expect;
3 | var cli = require('../lib/cli');
4 | var FakeJigsaw = require('./utils/fake-jigsaw');
5 |
6 | // Define test helpers
7 | function cliParse(argv, cb) {
8 | var stderr = '';
9 | var mockConsole = {
10 | error: function (output) {
11 | stderr += output + '\n';
12 | }
13 | };
14 | before(function cliParseFn (done) {
15 | var that = this;
16 | cli._parse(argv, mockConsole, function handleParse (err, status) {
17 | that.err = err;
18 | that.status = status;
19 | that.stderr = stderr;
20 | done();
21 | });
22 | });
23 | after(function cleanup () {
24 | delete this.err;
25 | delete this.status;
26 | delete this.stderr;
27 | });
28 | }
29 |
30 | describe('A valid CSS file processed by our CLI', function () {
31 | FakeJigsaw.run({multipart: true});
32 | cliParse([
33 | 'node', 'css-validator', __dirname + '/test-files/valid.css',
34 | '--w3c-url', FakeJigsaw.w3cUrl
35 | ]);
36 |
37 | it('has no errors', function () {
38 | expect(this.err).to.equal(null);
39 | expect(this.status).to.equal(0);
40 | expect(this.stderr).to.equal('');
41 | });
42 | });
43 |
44 | describe('An invalid CSS file processed by our CLI', function () {
45 | FakeJigsaw.run({multipart: true});
46 | cliParse([
47 | 'node', 'css-validator', __dirname + '/test-files/invalid.css',
48 | '--w3c-url', FakeJigsaw.w3cUrl
49 | ]);
50 |
51 | it('has no unexpected errors (e.g. bad URL)', function () {
52 | expect(this.err).to.equal(null);
53 | expect(this.status).to.equal(2);
54 | });
55 |
56 | it('outputs our expected error', function () {
57 | expect(this.stderr).to.contain('invalid.css:2:');
58 | expect(this.stderr).to.contain('background-color');
59 | });
60 |
61 | it('outputs our expected warning', function () {
62 | expect(this.stderr).to.contain('invalid.css:3:\n “-moz-box-sizing” is an unknown vendor extension');
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/lib/xml-parser.js:
--------------------------------------------------------------------------------
1 | // Load in dependencies
2 | var util = require('util');
3 |
4 | var extend = require('obj-extend');
5 | var Duplex = require('readable-stream').Duplex;
6 | var sax = require('sax');
7 |
8 | // Create XmlParser
9 | function XmlParser(options) {
10 | Duplex.call(this, options);
11 | var xmlParser = sax.createStream();
12 | this.xmlParser = xmlParser;
13 |
14 | // Set up listeners for validity OR error info
15 | var that = this;
16 | xmlParser.on('text', function (text) {
17 | var node = this._parser.tag;
18 | var nameNS = node.name.toLowerCase();
19 | if (nameNS === 'm:validity') {
20 | that.emit('validity', text === 'true');
21 | } else if (that.validationErr) {
22 | var name = nameNS.replace('m:', '');
23 | that.validationErr[name] = text;
24 | }
25 | });
26 |
27 | // Set up listeners to open/close new errors/warnings
28 | xmlParser.on('opentag', function (node) {
29 | switch (node.name.toLowerCase()) {
30 | case 'm:error':
31 | case 'm:warning':
32 | that.validationErr = {};
33 | break;
34 | }
35 | });
36 | xmlParser.on('closetag', function (name) {
37 | var node = this._parser.tag;
38 | switch (node.name.toLowerCase()) {
39 | case 'm:error':
40 | that.emit('validation-error', that.validationErr);
41 | that.validationErr = null;
42 | break;
43 | case 'm:warning':
44 | that.emit('validation-warning', that.validationErr);
45 | that.validationErr = null;
46 | break;
47 | case 'env:envelope':
48 | that.push(null);
49 | break;
50 | }
51 | });
52 |
53 | // Forward errors
54 | xmlParser.on('error', function (err) {
55 | that.emit('error', err);
56 | });
57 | }
58 | util.inherits(XmlParser, Duplex);
59 | extend(XmlParser.prototype, {
60 | _write: function (chunk, encoding, callback) {
61 | this.xmlParser.write(chunk);
62 | callback();
63 | },
64 | _read: function (size) {
65 | // We do not return any data. We only signal EOF.
66 | }
67 | });
68 | XmlParser.events = [
69 | 'validity',
70 | 'validation-error',
71 | 'validation-warning'
72 | ];
73 |
74 | module.exports = XmlParser;
75 |
--------------------------------------------------------------------------------
/.jscsrc:
--------------------------------------------------------------------------------
1 | {
2 | "requireCurlyBraces": [
3 | "if",
4 | "else",
5 | "for",
6 | "while",
7 | "do",
8 | "try",
9 | "catch",
10 | "finally",
11 | "with"
12 | ],
13 | "requireSpaceAfterKeywords": true,
14 | "requireSpaceBeforeBlockStatements": true,
15 | "requireSpacesInConditionalExpression": true,
16 | "requireSpacesInFunctionExpression": {
17 | "beforeOpeningRoundBrace": true,
18 | "beforeOpeningCurlyBrace": true
19 | },
20 | "requireSpacesInFunctionDeclaration": {
21 | "beforeOpeningCurlyBrace": true
22 | },
23 | "disallowSpacesInFunctionDeclaration": {
24 | "beforeOpeningRoundBrace": true
25 | },
26 | "disallowSpacesInCallExpression": true,
27 | "disallowMultipleVarDecl": true,
28 | "requireBlocksOnNewline": 1,
29 | "disallowPaddingNewlinesInBlocks": true,
30 | "disallowSpacesInsideObjectBrackets": "all",
31 | "disallowSpacesInsideArrayBrackets": "all",
32 | "disallowSpacesInsideParentheses": true,
33 | "disallowQuotedKeysInObjects": "allButReserved",
34 | "disallowSpaceAfterObjectKeys": true,
35 | "requireSpaceBeforeObjectValues": true,
36 | "requireCommaBeforeLineBreak": true,
37 | "requireOperatorBeforeLineBreak": true,
38 | "disallowSpaceAfterPrefixUnaryOperators": true,
39 | "disallowSpaceBeforePostfixUnaryOperators": true,
40 | "requireSpaceBeforeBinaryOperators": true,
41 | "requireSpaceAfterBinaryOperators": true,
42 | "requireCamelCaseOrUpperCaseIdentifiers": "ignoreProperties",
43 | "disallowKeywords": [
44 | "with"
45 | ],
46 | "disallowMultipleLineStrings": true,
47 | "disallowMultipleLineBreaks": true,
48 | "disallowMixedSpacesAndTabs": true,
49 | "disallowTrailingWhitespace": true,
50 | "disallowTrailingComma": true,
51 | "disallowKeywordsOnNewLine": [
52 | "else",
53 | "catch",
54 | "finally"
55 | ],
56 | "requireLineFeedAtFileEnd": true,
57 | "maximumLineLength": {
58 | "value": 120,
59 | "allowUrlComments": true
60 | },
61 | "requireDotNotation": true,
62 | "disallowYodaConditions": true,
63 | "requireSpaceAfterLineComment": true,
64 | "disallowNewlineBeforeBlockStatements": true,
65 | "validateLineBreaks": "LF",
66 | "validateQuoteMarks": {
67 | "mark": "'",
68 | "escape": true
69 | },
70 | "validateIndentation": 2,
71 | "validateParameterSeparator": ", ",
72 | "safeContextKeyword": [
73 | "that"
74 | ]
75 | }
76 |
--------------------------------------------------------------------------------
/test/test-files/fake-jigsaw/GET_%2Fcss-validator%2Fvalidator%3Fo_319e22637d6dc24aa73a46c650902961.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "httpVersion": "1.1",
4 | "headers": {
5 | "host": "localhost:1337",
6 | "connection": "close"
7 | },
8 | "trailers": {},
9 | "method": "GET",
10 | "url": "/css-validator/validator?output=soap12&w3cUrl=http%3A%2F%2Flocalhost%3A1337%2Fcss-validator%2Fvalidator&uri=https%3A%2F%2Fgitcdn.link%2Frepo%2Ftwolfson%2Fcss-validator%2F0.7.0%2Ftest%2Ftest-files%2Fvalid.css",
11 | "bodyEncoding": "utf8",
12 | "body": ""
13 | },
14 | "response": {
15 | "httpVersion": "1.1",
16 | "headers": {
17 | "cache-control": "no-cache",
18 | "date": "Sun, 07 Mar 2021 10:21:41 GMT",
19 | "pragma": "no-cache",
20 | "transfer-encoding": "chunked",
21 | "content-language": "en",
22 | "content-type": "application/soap+xml;charset=utf-8",
23 | "server": "Jigsaw/2.3.0-beta3",
24 | "vary": "Accept-Language",
25 | "access-control-allow-origin": "*",
26 | "access-control-allow-headers": "content-type,accept-charset",
27 | "access-control-allow-methods": "GET, HEAD, POST, OPTIONS",
28 | "access-control-max-age": "600",
29 | "x-w3c-validator-errors": "0",
30 | "x-w3c-validator-status": "Valid",
31 | "connection": "close"
32 | },
33 | "trailers": {},
34 | "statusCode": 200,
35 | "bodyEncoding": "utf8",
36 | "body": "\n\n \n \n https://gitcdn.link/repo/twolfson/css-validator/0.7.0/test/test-files/valid.css\n http://jigsaw.w3.org/css-validator/\n css3\n 2021-03-07T10:21:41Z\n true\n \n \n 0\n \n \n \n 0\n \n \n \n \n\n\n"
37 | }
38 | }
--------------------------------------------------------------------------------
/test/test-files/invalid.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
7 | file://localhost/TextArea
8 | http://jigsaw.w3.org/css-validator/
9 | css3
10 | 2013-11-27T10:38:34Z
11 | false
12 |
13 |
14 | 1
15 |
16 |
17 | file://localhost/TextArea
18 |
19 |
20 | 2
21 | parse-error
22 | body
23 |
24 | exp
25 |
26 |
27 | url(ab 'cd')
28 |
29 |
30 |
31 |
32 | Value Error : background (nullcolors.html#propdef-background)
33 |
34 | url(ab 'cd') is not a background-color value :
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 | 1
43 |
44 |
45 | file://localhost/TextArea
46 |
47 |
48 | 3
49 | 0
50 | Property -moz-box-sizing is an unknown vendor extension
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
--------------------------------------------------------------------------------
/test/test-files/fake-jigsaw/POST_%2Fcss-validator%2Fvalidator_526485bb7d0707a4b57a91373385e04e.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "httpVersion": "1.1",
4 | "headers": {
5 | "content-type": "multipart/form-data; boundary=--------------------------823789241125776521034436",
6 | "host": "localhost:1337",
7 | "connection": "close",
8 | "transfer-encoding": "chunked"
9 | },
10 | "trailers": {},
11 | "method": "POST",
12 | "url": "/css-validator/validator",
13 | "bodyEncoding": "utf8",
14 | "body": "----------------------------823789241125776521034436\r\nContent-Disposition: form-data; name=\"output\"\r\n\r\nsoap12\r\n----------------------------823789241125776521034436\r\nContent-Disposition: form-data; name=\"w3cUrl\"\r\n\r\nhttp://localhost:1337/css-validator/validator\r\n----------------------------823789241125776521034436\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\nbody {\n background: url(abcd);\n}\n\r\n----------------------------823789241125776521034436--"
15 | },
16 | "response": {
17 | "httpVersion": "1.1",
18 | "headers": {
19 | "cache-control": "no-cache",
20 | "date": "Sun, 07 Mar 2021 10:21:40 GMT",
21 | "pragma": "no-cache",
22 | "transfer-encoding": "chunked",
23 | "content-language": "en",
24 | "content-type": "application/soap+xml;charset=utf-8",
25 | "server": "Jigsaw/2.3.0-beta3",
26 | "vary": "Accept-Language",
27 | "access-control-allow-origin": "*",
28 | "access-control-allow-headers": "content-type,accept-charset",
29 | "access-control-allow-methods": "GET, HEAD, POST, OPTIONS",
30 | "access-control-max-age": "600",
31 | "x-w3c-validator-errors": "0",
32 | "x-w3c-validator-status": "Valid",
33 | "connection": "close"
34 | },
35 | "trailers": {},
36 | "statusCode": 200,
37 | "bodyEncoding": "utf8",
38 | "body": "\n\n \n \n TextArea\n http://jigsaw.w3.org/css-validator/\n css3\n 2021-03-07T10:21:40Z\n true\n \n \n 0\n \n \n \n 0\n \n \n \n \n\n\n"
39 | }
40 | }
--------------------------------------------------------------------------------
/test/test-files/fake-jigsaw/POST_%2Fcss-validator%2Fvalidator_aa857c07950aabae1aa1f97d95880a5e.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "httpVersion": "1.1",
4 | "headers": {
5 | "content-type": "multipart/form-data; boundary=--------------------------357882119082305388164373",
6 | "host": "localhost:1337",
7 | "connection": "close",
8 | "transfer-encoding": "chunked"
9 | },
10 | "trailers": {},
11 | "method": "POST",
12 | "url": "/css-validator/validator",
13 | "bodyEncoding": "utf8",
14 | "body": "----------------------------357882119082305388164373\r\nContent-Disposition: form-data; name=\"output\"\r\n\r\nsoap12\r\n----------------------------357882119082305388164373\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\nbody {\n background: url(abcd);\n}\n\r\n----------------------------357882119082305388164373\r\nContent-Disposition: form-data; name=\"w3cUrl\"\r\n\r\nhttp://localhost:1337/css-validator/validator\r\n----------------------------357882119082305388164373--"
15 | },
16 | "response": {
17 | "httpVersion": "1.1",
18 | "headers": {
19 | "cache-control": "no-cache",
20 | "date": "Sun, 07 Mar 2021 10:21:40 GMT",
21 | "pragma": "no-cache",
22 | "transfer-encoding": "chunked",
23 | "content-language": "en",
24 | "content-type": "application/soap+xml;charset=utf-8",
25 | "server": "Jigsaw/2.3.0-beta3",
26 | "vary": "Accept-Language",
27 | "access-control-allow-origin": "*",
28 | "access-control-allow-headers": "content-type,accept-charset",
29 | "access-control-allow-methods": "GET, HEAD, POST, OPTIONS",
30 | "access-control-max-age": "600",
31 | "x-w3c-validator-errors": "0",
32 | "x-w3c-validator-status": "Valid",
33 | "connection": "close"
34 | },
35 | "trailers": {},
36 | "statusCode": 200,
37 | "bodyEncoding": "utf8",
38 | "body": "\n\n \n \n TextArea\n http://jigsaw.w3.org/css-validator/\n css3\n 2021-03-07T10:21:40Z\n true\n \n \n 0\n \n \n \n 0\n \n \n \n \n\n\n"
39 | }
40 | }
--------------------------------------------------------------------------------
/test/test-files/fake-jigsaw/POST_%2Fcss-validator%2Fvalidator_d0fc59552c514a6cfde2b979f258009e.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "httpVersion": "1.1",
4 | "headers": {
5 | "content-type": "multipart/form-data; boundary=--------------------------461283002974093733751980",
6 | "host": "localhost:1337",
7 | "connection": "close",
8 | "transfer-encoding": "chunked"
9 | },
10 | "trailers": {},
11 | "method": "POST",
12 | "url": "/css-validator/validator",
13 | "bodyEncoding": "utf8",
14 | "body": "----------------------------461283002974093733751980\r\nContent-Disposition: form-data; name=\"output\"\r\n\r\nsoap12\r\n----------------------------461283002974093733751980\r\nContent-Disposition: form-data; name=\"w3cUrl\"\r\n\r\nhttp://localhost:1337/css-validator/validator\r\n----------------------------461283002974093733751980\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\nbody {\n background: url(abcd);\n}\n\r\n----------------------------461283002974093733751980--\r\n"
15 | },
16 | "response": {
17 | "httpVersion": "1.1",
18 | "headers": {
19 | "cache-control": "no-cache",
20 | "date": "Sun, 07 Mar 2021 10:25:09 GMT",
21 | "pragma": "no-cache",
22 | "transfer-encoding": "chunked",
23 | "content-language": "en",
24 | "content-type": "application/soap+xml;charset=utf-8",
25 | "server": "Jigsaw/2.3.0-beta3",
26 | "vary": "Accept-Language",
27 | "access-control-allow-origin": "*",
28 | "access-control-allow-headers": "content-type,accept-charset",
29 | "access-control-allow-methods": "GET, HEAD, POST, OPTIONS",
30 | "access-control-max-age": "600",
31 | "x-w3c-validator-errors": "0",
32 | "x-w3c-validator-status": "Valid",
33 | "connection": "close"
34 | },
35 | "trailers": {},
36 | "statusCode": 200,
37 | "bodyEncoding": "utf8",
38 | "body": "\n\n \n \n TextArea\n http://jigsaw.w3.org/css-validator/\n css3\n 2021-03-07T10:25:09Z\n true\n \n \n 0\n \n \n \n 0\n \n \n \n \n\n\n"
39 | }
40 | }
--------------------------------------------------------------------------------
/test/test-files/fake-jigsaw/POST_%2Fcss-validator%2Fvalidator_e635bbc34c4e4b6f66663de649b058e5.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "httpVersion": "1.1",
4 | "headers": {
5 | "content-type": "multipart/form-data; boundary=--------------------------081802144532091806951155",
6 | "host": "localhost:1337",
7 | "connection": "close",
8 | "transfer-encoding": "chunked"
9 | },
10 | "trailers": {},
11 | "method": "POST",
12 | "url": "/css-validator/validator",
13 | "bodyEncoding": "utf8",
14 | "body": "----------------------------081802144532091806951155\r\nContent-Disposition: form-data; name=\"output\"\r\n\r\nsoap12\r\n----------------------------081802144532091806951155\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\nbody {\n background: url(abcd);\n}\n\r\n----------------------------081802144532091806951155\r\nContent-Disposition: form-data; name=\"w3cUrl\"\r\n\r\nhttp://localhost:1337/css-validator/validator\r\n----------------------------081802144532091806951155--\r\n"
15 | },
16 | "response": {
17 | "httpVersion": "1.1",
18 | "headers": {
19 | "cache-control": "no-cache",
20 | "date": "Sun, 07 Mar 2021 10:25:08 GMT",
21 | "pragma": "no-cache",
22 | "transfer-encoding": "chunked",
23 | "content-language": "en",
24 | "content-type": "application/soap+xml;charset=utf-8",
25 | "server": "Jigsaw/2.3.0-beta3",
26 | "vary": "Accept-Language",
27 | "access-control-allow-origin": "*",
28 | "access-control-allow-headers": "content-type,accept-charset",
29 | "access-control-allow-methods": "GET, HEAD, POST, OPTIONS",
30 | "access-control-max-age": "600",
31 | "x-w3c-validator-errors": "0",
32 | "x-w3c-validator-status": "Valid",
33 | "connection": "close"
34 | },
35 | "trailers": {},
36 | "statusCode": 200,
37 | "bodyEncoding": "utf8",
38 | "body": "\n\n \n \n TextArea\n http://jigsaw.w3.org/css-validator/\n css3\n 2021-03-07T10:25:08Z\n true\n \n \n 0\n \n \n \n 0\n \n \n \n \n\n\n"
39 | }
40 | }
--------------------------------------------------------------------------------
/test/css-validator_test.js:
--------------------------------------------------------------------------------
1 | // Load in our dependencies
2 | var fs = require('fs');
3 | var expect = require('chai').expect;
4 | var extend = require('obj-extend');
5 | var validateCss = require('../');
6 | var FakeJigsaw = require('./utils/fake-jigsaw');
7 |
8 | // Define our test helper
9 | function _runValidateCss(paramsFn) {
10 | before(function (done) {
11 | var that = this;
12 | var params = paramsFn();
13 | validateCss(extend({
14 | w3cUrl: 'http://localhost:1337/css-validator/validator'
15 | }, params), function (err, data) {
16 | that.err = err;
17 | that.data = data;
18 | done();
19 | });
20 | });
21 | }
22 | function runValidateCssText(paramsFn) {
23 | FakeJigsaw.run({multipart: true});
24 | _runValidateCss(paramsFn);
25 | }
26 | function runValidateCssUri(paramsFn) {
27 | FakeJigsaw.run({multipart: false});
28 | _runValidateCss(paramsFn);
29 | }
30 |
31 | // Define our tests
32 | describe('A valid CSS file being validated', function () {
33 | runValidateCssText(function () {
34 | return {
35 | text: fs.readFileSync(__dirname + '/test-files/valid.css', 'utf8')
36 | };
37 | });
38 |
39 | it('has no errors', function () {
40 | expect(this.data.validity).to.equal(true);
41 | expect(this.data.errors).to.deep.equal([]);
42 | expect(this.data.warnings).to.deep.equal([]);
43 | });
44 | });
45 |
46 | describe('A invalid CSS file being validated', function () {
47 | runValidateCssText(function () {
48 | return {
49 | text: fs.readFileSync(__dirname + '/test-files/invalid.css', 'utf8')
50 | };
51 | });
52 |
53 | it('was not valid errors', function () {
54 | expect(this.data.validity).to.equal(false);
55 | });
56 |
57 | it('has an expected error', function () {
58 | var errors = this.data.errors;
59 | expect(errors.length).to.equal(1);
60 | expect(errors[0].message).to.contain('background-color');
61 | });
62 |
63 | it('has an expected warning', function () {
64 | var warnings = this.data.warnings;
65 | expect(warnings.length).to.equal(1);
66 | expect(warnings[0].message).to.contain('-moz-box-sizing');
67 | });
68 | });
69 |
70 | describe('A valid CSS URI being validated', function () {
71 | runValidateCssUri(function () {
72 | return {
73 | uri: 'https://gitcdn.link/repo/twolfson/css-validator/0.7.0/test/test-files/valid.css'
74 | };
75 | });
76 |
77 | it('has no errors', function () {
78 | expect(this.data.validity).to.equal(true);
79 | expect(this.data.errors).to.deep.equal([]);
80 | expect(this.data.warnings).to.deep.equal([]);
81 | });
82 | });
83 |
84 | describe('A invalid CSS URI being validated', function () {
85 | runValidateCssUri(function () {
86 | return {
87 | uri: 'https://gitcdn.link/repo/twolfson/css-validator/0.7.0/test/test-files/invalid.css'
88 | };
89 | });
90 |
91 | it('was not valid errors', function () {
92 | expect(this.data.validity).to.equal(false);
93 | });
94 |
95 | it('has an expected error', function () {
96 | var errors = this.data.errors;
97 | expect(errors.length).to.equal(1);
98 | expect(errors[0].message).to.contain('background-color');
99 | });
100 |
101 | it('has an expected warning', function () {
102 | var warnings = this.data.warnings;
103 | expect(warnings.length).to.equal(1);
104 | expect(warnings[0].message).to.contain('-moz-box-sizing');
105 | });
106 | });
107 |
108 | // Edge cases
109 | describe('An empty CSS file being validated', function () {
110 | runValidateCssText(function () {
111 | return {text: ''};
112 | });
113 |
114 | it('has no errors', function () {
115 | expect(this.data.validity).to.equal(true);
116 | expect(this.data.errors).to.deep.equal([]);
117 | expect(this.data.warnings).to.deep.equal([]);
118 | });
119 | });
120 |
121 | describe('A blank CSS file being validated', function () {
122 | runValidateCssText(function () {
123 | return {text: ' '};
124 | });
125 |
126 | it('has no errors', function () {
127 | expect(this.data.validity).to.equal(true);
128 | expect(this.data.errors).to.deep.equal([]);
129 | expect(this.data.warnings).to.deep.equal([]);
130 | });
131 | });
132 |
--------------------------------------------------------------------------------
/test/test-files/fake-jigsaw/GET_%2Fcss-validator%2Fvalidator%3Fo_866e5724035789665269ec61dc1641a4.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "httpVersion": "1.1",
4 | "headers": {
5 | "host": "localhost:1337",
6 | "connection": "close"
7 | },
8 | "trailers": {},
9 | "method": "GET",
10 | "url": "/css-validator/validator?output=soap12&w3cUrl=http%3A%2F%2Flocalhost%3A1337%2Fcss-validator%2Fvalidator&uri=https%3A%2F%2Fgitcdn.link%2Frepo%2Ftwolfson%2Fcss-validator%2F0.7.0%2Ftest%2Ftest-files%2Finvalid.css",
11 | "bodyEncoding": "utf8",
12 | "body": ""
13 | },
14 | "response": {
15 | "httpVersion": "1.1",
16 | "headers": {
17 | "cache-control": "no-cache",
18 | "date": "Sun, 07 Mar 2021 10:21:01 GMT",
19 | "pragma": "no-cache",
20 | "transfer-encoding": "chunked",
21 | "content-language": "en",
22 | "content-type": "application/soap+xml;charset=utf-8",
23 | "server": "Jigsaw/2.3.0-beta3",
24 | "vary": "Accept-Language",
25 | "access-control-allow-origin": "*",
26 | "access-control-allow-headers": "content-type,accept-charset",
27 | "access-control-allow-methods": "GET, HEAD, POST, OPTIONS",
28 | "access-control-max-age": "600",
29 | "x-w3c-validator-errors": "1",
30 | "x-w3c-validator-status": "Invalid",
31 | "connection": "close"
32 | },
33 | "trailers": {},
34 | "statusCode": 200,
35 | "bodyEncoding": "utf8",
36 | "body": "\n\n \n \n https://gitcdn.link/repo/twolfson/css-validator/0.7.0/test/test-files/invalid.css\n http://jigsaw.w3.org/css-validator/\n css3\n 2021-03-07T10:21:02Z\n false\n \n \n 1\n \n \n https://gitcdn.link/cdn/twolfson/css-validator/b694a83b93fa9ffb2150dd2fbab75223a49b3bd2/test/test-files/invalid.css\n \n \n 2\n parse-error\n body \n \n exp\n \n \n url(ab 'cd')\n \n value\n \n \n \n Value Error : background (nullcolors.html#propdef-background)\n \n “url(ab 'cd')” is not a “background-color” value : \n \n \n \n \n \n \n \n 1\n \n \n https://gitcdn.link/cdn/twolfson/css-validator/b694a83b93fa9ffb2150dd2fbab75223a49b3bd2/test/test-files/invalid.css\n \n \n 3\n 0\n “-moz-box-sizing” is an unknown vendor extension\n vendor-extension\n \n \n \n \n \n \n \n\n\n"
37 | }
38 | }
--------------------------------------------------------------------------------
/test/test-files/fake-jigsaw/POST_%2Fcss-validator%2Fvalidator_70e94cf96374050f7556586668bf6184.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "httpVersion": "1.1",
4 | "headers": {
5 | "content-type": "multipart/form-data; boundary=--------------------------322023314191257846612438",
6 | "host": "localhost:1337",
7 | "connection": "close",
8 | "transfer-encoding": "chunked"
9 | },
10 | "trailers": {},
11 | "method": "POST",
12 | "url": "/css-validator/validator",
13 | "bodyEncoding": "utf8",
14 | "body": "----------------------------322023314191257846612438\r\nContent-Disposition: form-data; name=\"output\"\r\n\r\nsoap12\r\n----------------------------322023314191257846612438\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\nbody {\n background: url(ab'cd');\n -moz-box-sizing: content-box;\n}\n\n\r\n----------------------------322023314191257846612438\r\nContent-Disposition: form-data; name=\"w3cUrl\"\r\n\r\nhttp://localhost:1337/css-validator/validator\r\n----------------------------322023314191257846612438--"
15 | },
16 | "response": {
17 | "httpVersion": "1.1",
18 | "headers": {
19 | "cache-control": "no-cache",
20 | "date": "Sun, 07 Mar 2021 10:21:40 GMT",
21 | "pragma": "no-cache",
22 | "transfer-encoding": "chunked",
23 | "content-language": "en",
24 | "content-type": "application/soap+xml;charset=utf-8",
25 | "server": "Jigsaw/2.3.0-beta3",
26 | "vary": "Accept-Language",
27 | "access-control-allow-origin": "*",
28 | "access-control-allow-headers": "content-type,accept-charset",
29 | "access-control-allow-methods": "GET, HEAD, POST, OPTIONS",
30 | "access-control-max-age": "600",
31 | "x-w3c-validator-errors": "1",
32 | "x-w3c-validator-status": "Invalid",
33 | "connection": "close"
34 | },
35 | "trailers": {},
36 | "statusCode": 200,
37 | "bodyEncoding": "utf8",
38 | "body": "\n\n \n \n TextArea\n http://jigsaw.w3.org/css-validator/\n css3\n 2021-03-07T10:21:40Z\n false\n \n \n 1\n \n \n file://localhost/TextArea\n \n \n 2\n parse-error\n body \n \n exp\n \n \n url(ab 'cd')\n \n value\n \n \n \n Value Error : background (nullcolors.html#propdef-background)\n \n “url(ab 'cd')” is not a “background-color” value : \n \n \n \n \n \n \n \n 1\n \n \n file://localhost/TextArea\n \n \n 3\n 0\n “-moz-box-sizing” is an unknown vendor extension\n vendor-extension\n \n \n \n \n \n \n \n\n\n"
39 | }
40 | }
--------------------------------------------------------------------------------
/test/test-files/fake-jigsaw/POST_%2Fcss-validator%2Fvalidator_d56efd3da2bc6ea5b0c8346843f619dc.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "httpVersion": "1.1",
4 | "headers": {
5 | "content-type": "multipart/form-data; boundary=--------------------------490158434652554678086072",
6 | "host": "localhost:1337",
7 | "connection": "close",
8 | "transfer-encoding": "chunked"
9 | },
10 | "trailers": {},
11 | "method": "POST",
12 | "url": "/css-validator/validator",
13 | "bodyEncoding": "utf8",
14 | "body": "----------------------------490158434652554678086072\r\nContent-Disposition: form-data; name=\"output\"\r\n\r\nsoap12\r\n----------------------------490158434652554678086072\r\nContent-Disposition: form-data; name=\"w3cUrl\"\r\n\r\nhttp://localhost:1337/css-validator/validator\r\n----------------------------490158434652554678086072\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\nbody {\n background: url(ab'cd');\n -moz-box-sizing: content-box;\n}\n\n\r\n----------------------------490158434652554678086072--"
15 | },
16 | "response": {
17 | "httpVersion": "1.1",
18 | "headers": {
19 | "cache-control": "no-cache",
20 | "date": "Sun, 07 Mar 2021 10:21:41 GMT",
21 | "pragma": "no-cache",
22 | "transfer-encoding": "chunked",
23 | "content-language": "en",
24 | "content-type": "application/soap+xml;charset=utf-8",
25 | "server": "Jigsaw/2.3.0-beta3",
26 | "vary": "Accept-Language",
27 | "access-control-allow-origin": "*",
28 | "access-control-allow-headers": "content-type,accept-charset",
29 | "access-control-allow-methods": "GET, HEAD, POST, OPTIONS",
30 | "access-control-max-age": "600",
31 | "x-w3c-validator-errors": "1",
32 | "x-w3c-validator-status": "Invalid",
33 | "connection": "close"
34 | },
35 | "trailers": {},
36 | "statusCode": 200,
37 | "bodyEncoding": "utf8",
38 | "body": "\n\n \n \n TextArea\n http://jigsaw.w3.org/css-validator/\n css3\n 2021-03-07T10:21:41Z\n false\n \n \n 1\n \n \n file://localhost/TextArea\n \n \n 2\n parse-error\n body \n \n exp\n \n \n url(ab 'cd')\n \n value\n \n \n \n Value Error : background (nullcolors.html#propdef-background)\n \n “url(ab 'cd')” is not a “background-color” value : \n \n \n \n \n \n \n \n 1\n \n \n file://localhost/TextArea\n \n \n 3\n 0\n “-moz-box-sizing” is an unknown vendor extension\n vendor-extension\n \n \n \n \n \n \n \n\n\n"
39 | }
40 | }
--------------------------------------------------------------------------------
/test/test-files/fake-jigsaw/POST_%2Fcss-validator%2Fvalidator_b01b6dda79fbb8a05a4313bd36d18166.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "httpVersion": "1.1",
4 | "headers": {
5 | "content-type": "multipart/form-data; boundary=--------------------------907739183840313930238424",
6 | "host": "localhost:1337",
7 | "connection": "close",
8 | "transfer-encoding": "chunked"
9 | },
10 | "trailers": {},
11 | "method": "POST",
12 | "url": "/css-validator/validator",
13 | "bodyEncoding": "utf8",
14 | "body": "----------------------------907739183840313930238424\r\nContent-Disposition: form-data; name=\"output\"\r\n\r\nsoap12\r\n----------------------------907739183840313930238424\r\nContent-Disposition: form-data; name=\"w3cUrl\"\r\n\r\nhttp://localhost:1337/css-validator/validator\r\n----------------------------907739183840313930238424\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\nbody {\n background: url(ab'cd');\n -moz-box-sizing: content-box;\n}\n\n\r\n----------------------------907739183840313930238424--\r\n"
15 | },
16 | "response": {
17 | "httpVersion": "1.1",
18 | "headers": {
19 | "cache-control": "no-cache",
20 | "date": "Sun, 07 Mar 2021 10:25:09 GMT",
21 | "pragma": "no-cache",
22 | "transfer-encoding": "chunked",
23 | "content-language": "en",
24 | "content-type": "application/soap+xml;charset=utf-8",
25 | "server": "Jigsaw/2.3.0-beta3",
26 | "vary": "Accept-Language",
27 | "access-control-allow-origin": "*",
28 | "access-control-allow-headers": "content-type,accept-charset",
29 | "access-control-allow-methods": "GET, HEAD, POST, OPTIONS",
30 | "access-control-max-age": "600",
31 | "x-w3c-validator-errors": "1",
32 | "x-w3c-validator-status": "Invalid",
33 | "connection": "close"
34 | },
35 | "trailers": {},
36 | "statusCode": 200,
37 | "bodyEncoding": "utf8",
38 | "body": "\n\n \n \n TextArea\n http://jigsaw.w3.org/css-validator/\n css3\n 2021-03-07T10:25:09Z\n false\n \n \n 1\n \n \n file://localhost/TextArea\n \n \n 2\n parse-error\n body \n \n exp\n \n \n url(ab 'cd')\n \n value\n \n \n \n Value Error : background (nullcolors.html#propdef-background)\n \n “url(ab 'cd')” is not a “background-color” value : \n \n \n \n \n \n \n \n 1\n \n \n file://localhost/TextArea\n \n \n 3\n 0\n “-moz-box-sizing” is an unknown vendor extension\n vendor-extension\n \n \n \n \n \n \n \n\n\n"
39 | }
40 | }
--------------------------------------------------------------------------------
/test/test-files/fake-jigsaw/POST_%2Fcss-validator%2Fvalidator_bac997c6520131945d11659def6876b3.json:
--------------------------------------------------------------------------------
1 | {
2 | "request": {
3 | "httpVersion": "1.1",
4 | "headers": {
5 | "content-type": "multipart/form-data; boundary=--------------------------540950892174155717799911",
6 | "host": "localhost:1337",
7 | "connection": "close",
8 | "transfer-encoding": "chunked"
9 | },
10 | "trailers": {},
11 | "method": "POST",
12 | "url": "/css-validator/validator",
13 | "bodyEncoding": "utf8",
14 | "body": "----------------------------540950892174155717799911\r\nContent-Disposition: form-data; name=\"output\"\r\n\r\nsoap12\r\n----------------------------540950892174155717799911\r\nContent-Disposition: form-data; name=\"text\"\r\n\r\nbody {\n background: url(ab'cd');\n -moz-box-sizing: content-box;\n}\n\n\r\n----------------------------540950892174155717799911\r\nContent-Disposition: form-data; name=\"w3cUrl\"\r\n\r\nhttp://localhost:1337/css-validator/validator\r\n----------------------------540950892174155717799911--\r\n"
15 | },
16 | "response": {
17 | "httpVersion": "1.1",
18 | "headers": {
19 | "cache-control": "no-cache",
20 | "date": "Sun, 07 Mar 2021 10:25:09 GMT",
21 | "pragma": "no-cache",
22 | "transfer-encoding": "chunked",
23 | "content-language": "en",
24 | "content-type": "application/soap+xml;charset=utf-8",
25 | "server": "Jigsaw/2.3.0-beta3",
26 | "vary": "Accept-Language",
27 | "access-control-allow-origin": "*",
28 | "access-control-allow-headers": "content-type,accept-charset",
29 | "access-control-allow-methods": "GET, HEAD, POST, OPTIONS",
30 | "access-control-max-age": "600",
31 | "x-w3c-validator-errors": "1",
32 | "x-w3c-validator-status": "Invalid",
33 | "connection": "close"
34 | },
35 | "trailers": {},
36 | "statusCode": 200,
37 | "bodyEncoding": "utf8",
38 | "body": "\n\n \n \n TextArea\n http://jigsaw.w3.org/css-validator/\n css3\n 2021-03-07T10:25:09Z\n false\n \n \n 1\n \n \n file://localhost/TextArea\n \n \n 2\n parse-error\n body \n \n exp\n \n \n url(ab 'cd')\n \n value\n \n \n \n Value Error : background (nullcolors.html#propdef-background)\n \n “url(ab 'cd')” is not a “background-color” value : \n \n \n \n \n \n \n \n 1\n \n \n file://localhost/TextArea\n \n \n 3\n 0\n “-moz-box-sizing” is an unknown vendor extension\n vendor-extension\n \n \n \n \n \n \n \n\n\n"
39 | }
40 | }
--------------------------------------------------------------------------------
/lib/cli.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | // Load in our dependencies
3 | var async = require('async');
4 | var fs = require('fs');
5 | var Command = require('commander').Command;
6 | var ValidationStream = require('../lib/validation-stream');
7 | var package = require('../package.json');
8 | var validateCss = require('../lib/css-validator');
9 |
10 | // Define our constants
11 | var DEFAULT_SLEEP_MS = 100;
12 | var DEFAULT_CONCURRENCY = 1;
13 | var STATUS_OK = 0;
14 | var STATUS_WARN = 1;
15 | var STATUS_ERR = 2;
16 |
17 | // Define our program
18 | exports._parse = function (argv, _console, callback) {
19 | // Define our program
20 | // https://github.com/tj/commander.js/blob/v2.9.0/index.js#L17
21 | var program = new Command();
22 | program
23 | .version(package.version)
24 | .usage('[options] ')
25 | .option('--w3c-url ', 'URL to validate against. Default is ' + ValidationStream.w3cUrl)
26 | // Waiting between calls is recommend: https://jigsaw.w3.org/css-validator/about.html#api
27 | .option('--delay ',
28 | 'Delay between validation requests to avoid service blacklisting, defaults to ' + DEFAULT_SLEEP_MS + 'ms',
29 | DEFAULT_SLEEP_MS)
30 | .option('--concurrency ',
31 | 'Amount of requests to run in parallel, defaults to ' + DEFAULT_CONCURRENCY, DEFAULT_CONCURRENCY)
32 | .option('--usermedium ',
33 | 'Medium where the CSS will be used (e.g. `all` (service default), `print`, `screen`)')
34 | .option('--profile ',
35 | 'CSS profile to use for validation (e.g. `css3svg` (service default), `css21`, `svg`)')
36 | .option('--lang ', 'Language to use in response (e.g. `en` (service default), `bg`, `de`)')
37 | .option('--warning ', 'Warning level to set (e.g. `0`, `1`, `2` (service default), `no`)')
38 | .option('--vextwarning ',
39 | 'Allow vendor extensions to just show up as warnings. ' +
40 | 'Possible values are: `true`, `false` (service default)');
41 |
42 | // Process our arguments
43 | program.parse(argv);
44 |
45 | // Get program opts
46 | var opts = program.opts();
47 |
48 | // Assume we are OK by default
49 | var status = STATUS_OK;
50 |
51 | // Load our files in parallel (eliminates existence errors upfront)
52 | var filepaths = program.args;
53 | async.map(filepaths, function loadFilepath (filepath, cb) {
54 | fs.readFile(filepath, 'utf8', cb);
55 | }, function handleLoadFilepaths (err, filepathContentArr) {
56 | // If there was an error, callback with it
57 | if (err) {
58 | return callback(err);
59 | }
60 |
61 | // Otherwise, perform our validation
62 | async.eachOfLimit(filepathContentArr, opts.concurrency,
63 | function handleFilepathContent (filepathContent, i, cb) {
64 | var filepath = filepaths[i];
65 | var options = {
66 | text: filepathContent,
67 | w3cUrl: opts.w3cUrl,
68 | usermedium: opts.usermedium,
69 | profile: opts.profile,
70 | lang: opts.lang,
71 | warning: opts.warning,
72 | vextwarning: opts.vextwarning
73 | };
74 |
75 | validateCss(options, function handleValidateCss (err, data) {
76 | // If we had an error, then callback with it
77 | if (err) {
78 | return cb(err);
79 | }
80 |
81 | // Output our errors and adjust our status
82 | if (data.warnings.length) {
83 | data.warnings.forEach(function handleWarning (warningObj) {
84 | // index.css:3:
85 | // Property -moz-box-sizing is an unknown vendor extension
86 | _console.error(filepath + ':' + warningObj.line + ':\n ' + warningObj.message);
87 | });
88 | status = Math.max(STATUS_WARN, status);
89 | }
90 | if (data.errors.length) {
91 | data.errors.forEach(function handleError (errorObj) {
92 | // index.css:2:
93 | // Value Error : background (nullcolors.html#propdef-background)
94 | // url(ab \'cd\') is not a background-color value
95 | var cleanedMesssge = errorObj.message
96 | .replace(/\n \n /g, '\n ')
97 | .replace(/\n\s+$/, '');
98 | _console.error(filepath + ':' + errorObj.line + ': ' + cleanedMesssge);
99 | });
100 | status = Math.max(STATUS_ERR, status);
101 | }
102 |
103 | // Continue
104 | setTimeout(cb, opts.delay);
105 | });
106 | }, function handleResults (err) {
107 | // If there was an error, callback with it
108 | if (err) {
109 | return callback(err);
110 | }
111 |
112 | // Otherwise, callback with our status code
113 | callback(null, status);
114 | });
115 | });
116 | };
117 | exports.parse = function (argv) {
118 | exports._parse(argv, global.console, function handleResults (err, status) {
119 | // If there was an error, throw it
120 | if (err) {
121 | throw err;
122 | }
123 |
124 | // Otherwise, exit with our status code
125 | process.exit(status);
126 | });
127 | };
128 |
--------------------------------------------------------------------------------
/lib/validation-stream.js:
--------------------------------------------------------------------------------
1 | // Load in dependencies
2 | var http = require('http');
3 | var https = require('https'); // jigsaw.w3.org requires https from March 2022 or perhaps earlier
4 | var querystring = require('querystring');
5 | var url = require('url');
6 | var util = require('util');
7 |
8 | var FormData = require('form-data');
9 | var extend = require('obj-extend');
10 | var Readable = require('readable-stream').Readable;
11 |
12 | var XmlParser = require('./xml-parser');
13 |
14 | // Define our constants
15 | var TYPE_URI = 'uri';
16 | var TYPE_TEXT = 'text';
17 |
18 | // We must make https:// requests with the https lib,
19 | // and http:// requests with the http lib. They error if they are given the wrong protocol.
20 | // Default to http (no 's') to maintain consistency with the pre-https versions of this file.
21 | function httpRequester(url) {
22 | return url && url.startsWith('https://') ? https : http;
23 | }
24 |
25 | // TODO: Consider this structure some more. It is good little parts but nothing is that reusable...
26 | function ValidationStream(options) {
27 | // If options is a string, upcast it to an object
28 | if (typeof options === 'string') {
29 | var css = options;
30 | options = {text: css};
31 | }
32 |
33 | // Clone the data to prevent mutation
34 | options = extend({}, options, {
35 | objectMode: true
36 | });
37 | this.options = options;
38 |
39 | // Inheret from Readable
40 | Readable.call(this, this.options);
41 |
42 | // Resolve our target type
43 | var type;
44 | if (this.options.hasOwnProperty('text')) {
45 | type = TYPE_TEXT;
46 | } else if (this.options.hasOwnProperty('uri')) {
47 | type = TYPE_URI;
48 | } else {
49 | throw new Error('No `uri` or `text` option was provided to css-validator');
50 | }
51 |
52 | // If we have non-empty CSS
53 | var hasContent = type === TYPE_TEXT ? /\S/.test(this.options.text) : true;
54 | if (hasContent) {
55 | // Generate our request
56 | if (type === TYPE_TEXT) {
57 | this.generateTextRequest(this.options.text);
58 | } else {
59 | this.generateUriRequest(this.options.uri);
60 | }
61 | // Otherwise (empty CSS), emit no results
62 | } else {
63 | // DEV: The W3C service only understands nonempty CSS yet we think that should be supported
64 | // DEV: We use `process.nextTick` to avoid zalgo
65 | var that = this;
66 | process.nextTick(function handleNextTick () {
67 | that._readEmptyCss();
68 | });
69 | }
70 | }
71 | util.inherits(ValidationStream, Readable);
72 | extend(ValidationStream.prototype, {
73 | generateTextRequest: function (text) {
74 | // Grab the URL we are going to POST to
75 | var options = this.options;
76 | var w3cUrl = options.w3cUrl || ValidationStream.w3cUrl;
77 |
78 | // Open the request
79 | var urlParts = url.parse(w3cUrl);
80 | var form = new FormData();
81 | urlParts.method = 'POST';
82 | urlParts.headers = form.getHeaders();
83 | var req = httpRequester(w3cUrl).request(urlParts);
84 |
85 | // https://jigsaw.w3.org/css-validator/api.html
86 | form.append('output', 'soap12');
87 | Object.getOwnPropertyNames(options).forEach(function sendOption (key) {
88 | var val = options[key];
89 | if (val && key !== 'output' && key !== 'objectMode') {
90 | form.append(key, val);
91 | }
92 | });
93 |
94 | // Pipe the form and close the request
95 | form.pipe(req);
96 | req.end();
97 |
98 | // Listen to our response
99 | this.listenForResponse(req);
100 | },
101 | generateUriRequest: function (text) {
102 | // Grab the URL we are going to POST to
103 | var options = this.options;
104 | var w3cUrl = options.w3cUrl || ValidationStream.w3cUrl;
105 |
106 | // Prepare our query string data
107 | // https://jigsaw.w3.org/css-validator/api.html
108 | var qsDataObj = {output: 'soap12'};
109 | Object.getOwnPropertyNames(options).forEach(function sendOption (key) {
110 | var val = options[key];
111 | if (val && key !== 'output' && key !== 'objectMode') {
112 | qsDataObj[key] = val;
113 | }
114 | });
115 |
116 | // Open the request
117 | // https://nodejs.org/api/http.html#http_http_request_options_callback
118 | var urlParts = url.parse(w3cUrl);
119 | urlParts.method = 'GET';
120 | urlParts.path += urlParts.path.indexOf('?') === -1 ? '?' : '&';
121 | urlParts.path += querystring.stringify(qsDataObj);
122 | var req = httpRequester(w3cUrl).request(urlParts);
123 |
124 | // Close the request
125 | req.end();
126 |
127 | // Listen to our response
128 | this.listenForResponse(req);
129 | },
130 | listenForResponse: function (req) {
131 | // TODO: Use a streaming form library (unfortunately, there were none)
132 | // Listen for errors from the request
133 | var that = this;
134 | req.on('error', function (err) {
135 | that.emit('error', err);
136 | });
137 |
138 | // Listen for the response
139 | req.on('response', function (response) {
140 | // Create a validator and pipe in the response
141 | var parser = new XmlParser();
142 | response.pipe(parser);
143 | parser.on('unpipe', function () {
144 | parser.end();
145 | });
146 |
147 | // Forward events back to the that
148 | XmlParser.events.forEach(function bindParserEvents (event) {
149 | parser.on(event, function forwardParserEvent (data) {
150 | that.emit(event, data);
151 | });
152 | });
153 |
154 | // Forward error events as well
155 | parser.on('error', function forwardParserErrors (err) {
156 | that.emit('error', err);
157 | });
158 |
159 | // Collect validation results
160 | // TODO: This should be able to go in the XmlParser
161 | // For example: parser.aggregateData();
162 | var validationErrors = [];
163 | var validationWarnings = [];
164 | var result = {
165 | validity: false,
166 | errors: validationErrors,
167 | warnings: validationWarnings
168 | };
169 | parser.on('validity', function (validity) {
170 | result.validity = validity;
171 | });
172 | parser.on('validation-error', function (err) {
173 | validationErrors.push(err);
174 | });
175 | parser.on('validation-warning', function (warning) {
176 | validationWarnings.push(warning);
177 | });
178 |
179 | // When we can, immediately force a read
180 | parser.on('readable', function () {
181 | parser.read();
182 |
183 | // When the parser is complete
184 | parser.on('end', function () {
185 | // Emit the result and EOF
186 | that.push(result);
187 | that.push(null);
188 | });
189 | });
190 | });
191 | },
192 | _read: function (size) {
193 | // DEV: Do not take any action as we only emit one data event, the result
194 | },
195 | _readEmptyCss: function () {
196 | var result = {
197 | validity: true,
198 | errors: [],
199 | warnings: []
200 | };
201 | this.push(result);
202 | this.push(null);
203 | }
204 | });
205 |
206 | // Set up reference for default end point
207 | // https://jigsaw.w3.org/css-validator/manual.html#api
208 | ValidationStream.w3cUrl = 'https://jigsaw.w3.org/css-validator/validator';
209 |
210 | module.exports = ValidationStream;
211 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # css-validator [](https://travis-ci.org/twolfson/css-validator)
2 |
3 | Validate CSS via [W3C's service][jigsaw]
4 |
5 | [jigsaw]: http://jigsaw.w3.org/css-validator/
6 |
7 | This was created to validate CSS inside of the [json2css][] test suite.
8 |
9 | [json2css]: https://github.com/twolfson/json2css
10 |
11 | ## Getting Started
12 | Install the module with: `npm install css-validator`
13 |
14 | ```js
15 | var validateCss = require('css-validator');
16 | var assert = require('assert');
17 | validateCss({text: 'a { color: blue; }'}, function (err, data) {
18 | assert.strictEqual(data.validity, true);
19 | assert.deepEqual(data.errors, []);
20 | assert.deepEqual(data.warnings, []);
21 | });
22 | ```
23 |
24 | ## Donations
25 | Support this project and [others by twolfson][projects] via [donations][support-me]
26 |
27 | [projects]: http://twolfson.com/projects
28 | [support-me]: http://twolfson.com/support-me
29 |
30 | ## Documentation
31 | `css-validator` returns a single function as its `module.exports`
32 |
33 | ### `validateCss(options, cb)`
34 | Validate CSS against [W3C's Jigsaw validation service][jigsaw]
35 |
36 | - options `String|Object` - If `options` is a `String`, it will be treated as `options.text`
37 | - w3cUrl `String` - URL to validate against. Default is http://jigsaw.w3.org/css-validator/validator
38 | - The following options from the validator itself
39 | - Reference: http://jigsaw.w3.org/css-validator/manual.html#api
40 | - uri `null|String` - URL of document to validate. CSS and HTML documents are allowed. Supports http and https protocols.
41 | - text `null|String` - CSS to validate
42 | - usermedium `String` - Medium where the CSS will be used (e.g. `all`, `print`, `screen`)
43 | - Service's default value: `all`
44 | - profile `String` - CSS profile to use for validation (e.g. `css3svg`, `css21`, `svg`)
45 | - Service's default value: `css3svg`
46 | - lang `String` - Language to use in response (e.g. `en`, `bg`, `de`)
47 | - Service's default value: `en`
48 | - warning `Number|String` - Warning level to set. Default is `2`
49 | - Service's default value: `2`
50 | - If set to `no`, no warnings will be returned
51 | - If set to `0`, less warnings will be returned
52 | - If set to `1` or `2`, more warnings will be returned
53 | - vextwarning `String|Boolean` - Allow vendor extensions to just show up as warnings
54 | - Possible values: `false`, `true`
55 | - Service's default value: `false`
56 | - cb `null|Function` - Error first callback with `function (err, data) {}` signature
57 | - err `null|Error` - If there was a connetivity error, this will be it
58 | - data `null|Object` - Container for response from [jigsaw][]
59 | - validity `Boolean` - If there were no errors, this will be `true`. Otherwise, it is `false`.
60 | - errors `Object[]` - Array of errors
61 | - These are dynamically parsed and not guaranteed to exist. The service only guarantees `line`, `level`, and `message`.
62 | - Reference: http://jigsaw.w3.org/css-validator/api.html#soap12message
63 | - line `Number` - Line where error occurred
64 | - errortype `String`
65 | - context `String`
66 | - errorsubtype `String`
67 | - skippedstring `String` - Content where error occurred
68 | - message `String` - Human readable information about the error and why it occurred
69 | - warnings `Object[]` - Array of warnings
70 | - line `Number` - Line where error occurred
71 | - level `Number` - Intensity of the warning. See `options.warning` for more info
72 | - message `String` - Human readable information about the warning and why it occurred
73 |
74 | If `cb` is not provided, a [`DuplexStream`][] will be returned to you.
75 |
76 | If you have not provided `options.uri` or `options.text`, you can `.write` + `.end` OR `.pipe` to the stream CSS to validate.
77 |
78 | Additionally, you can use `.read` and `.pipe` to get the `data` returned by `cb`.
79 |
80 | The stream will emit the following events:
81 |
82 | - error `Error` - Error occurring during connection or parsing of response
83 | - data `Object` - Same as `data` sent to `cb`. Emitted once.
84 | - end - Emitted when we have finished parsing the input and outputting events
85 | - validity `Boolean` - Event for `data.validity` with `data.validity` as its data
86 | - validation-error `Object` - Event for a new `data.errors` object with the error as its argument
87 | - validation-warning `Object` - Event for a new `data.warnings` object with the warning as its argument
88 |
89 | [`DuplexStream`]: https://github.com/isaacs/readable-stream#class-streamduplex
90 |
91 | ### CLI
92 | `css-validator` offers a command line interface for validation:
93 |
94 | ```
95 | $ css-validator --help
96 |
97 | Usage: css-validator [options]
98 |
99 | Options:
100 |
101 | -h, --help output usage information
102 | -V, --version output the version number
103 | --w3c-url URL to validate against. Default is http://jigsaw.w3.org/css-validator/validator
104 | --delay Delay between validation requests to avoid service blacklisting, defaults to 100ms
105 | --concurrency Amount of requests to run in parallel, defaults to 1
106 | --usermedium Medium where the CSS will be used (e.g. `all` (service default), `print`, `screen`)
107 | --profile CSS profile to use for validation (e.g. `css3svg` (service default), `css21`, `svg`)
108 | --lang Language to use in response (e.g. `en` (service default), `bg`, `de`)
109 | --warning Warning level to set (e.g. `0`, `1`, `2` (service default), `no`)
110 | --vextwarning Allow vendor extensions to just show up as warnings. Possible values are: `true`, `false` (service default)
111 | ```
112 |
113 | ## Examples
114 | ```js
115 | var cssValidate = require('css-validator');
116 | var css = [
117 | "body {",
118 | " background: url(ab'cd');",
119 | " -moz-box-sizing: content-box;",
120 | "}",
121 | ].join('\n');
122 |
123 | cssValidate(css, function (err, data) {
124 | console.log(data);
125 | /*
126 | { validity: false,
127 | errors:
128 | [ { line: '2',
129 | errortype: 'parse-error',
130 | context: ' body ',
131 | errorsubtype: '\n exp\n ',
132 | skippedstring: '\n url(ab \'cd\')\n ',
133 | message: '\n \n Value Error : background (nullcolors.html#propdef-background)\n \n url(ab \'cd\') is not a background-color value : \n ',
134 | error: '\n ' } ],
135 | warnings:
136 | [ { line: '3',
137 | level: '0',
138 | message: 'Property -moz-box-sizing is an unknown vendor extension',
139 | warning: '\n ' } ] }
140 | */
141 | });
142 | ```
143 |
144 | ## Contributing
145 | In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint via `npm run lint` and test via `npm test`.
146 |
147 | ## Unlicense
148 | As of Nov 27 2013, Todd Wolfson has released this repository and its contents to the public domain.
149 |
150 | It has been released under the [UNLICENSE][].
151 |
152 | [UNLICENSE]: UNLICENSE
153 |
--------------------------------------------------------------------------------