Skip to content

Commit

Permalink
feat(linter): Respec linter (#866)
Browse files Browse the repository at this point in the history
* feat(linter): create respec linter
* feat(profile-w3c-common): add linter to plugins
* chore(package): run jshint/jscs on linter.js
* feat(linter): linter HTTP URLs in config (closes #814)
 * add findHTTPProps() method to linter
 * add logic to run() method re: findHTTPProps
 * style(linter-spec): fix typo
  • Loading branch information
Marcos Cáceres committed Jul 14, 2016
1 parent 95cabd8 commit 10d2429
Show file tree
Hide file tree
Showing 6 changed files with 244 additions and 2 deletions.
1 change: 1 addition & 0 deletions js/profile-w3c-common.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ define([
"ui/dfn-list",
"ui/save-html",
"ui/search-specref",
"w3c/linter",
],
function(domReady, runner, ui) {
var args = Array.from(arguments);
Expand Down
71 changes: 71 additions & 0 deletions js/w3c/linter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
"use strict";
define(["core/pubsubhub"], function(pubsubhub) {
/**
* Checks for privacy and security and considerations heading. If "privacy" or
* "security", and "considerations", in any order, case-insensitive,
* multi-line check.
*
* @param {Document} doc The document to be checked.
* @return {Boolean} Returns true if section is found.
*/
function hasPriSecConsiderations(doc) {
return Array
.from(doc.querySelectorAll("h2, h3, h4, h5, h6"))
.map(function(elem) {
return elem.textContent;
})
.some(function(text) {
var privOrSecRegex = /(privacy|security)/igm;
var considerationsRegex = /(considerations)/igm;
return privOrSecRegex.test(text) && considerationsRegex.test(text);
});
}

function findHTTPProps(conf, base) {
return Object.getOwnPropertyNames(conf)
.filter(function(key) {
return key.endsWith("URI") || key === "prevED";
})
.filter(function(key) {
return new URL(conf[key], base).href.startsWith("http://");
});
}

return {
run: function(conf, doc, cb) {
if (!conf.lint || conf.status === "unofficial") {
return cb();
}
var warnings = [];
var warn = "";

// Warn if no privacy and/or security considerations section
if (!hasPriSecConsiderations(doc)) {
warn = "This specification doesn't appear to have any 'Privacy' " +
" or 'Security' considerations sections. Please consider adding one" +
", see https://w3ctag.github.io/security-questionnaire/";
warnings.push(warn);
}

// Warn about HTTP URLs used in respecConfig
var httpURLs = findHTTPProps(conf, doc.location.href);
if (httpURLs.length) {
warn = "There are insecure URLs in your respecConfig! Please change " +
"the following properties to use 'https://': " + httpURLs.join(", ") + ".";
warnings.push(warn);
}

// Publish warnings
warnings.map(function(warn) {
pubsubhub.pub("warning", warn);
});

cb();
},
// Convenience methods, for quickly testing rules.
rules: {
"findHTTPProps": findHTTPProps,
"hasPriSecConsiderations": hasPriSecConsiderations,
},
};
});
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@
"build": "npm run build:highlight; npm run build:respec-w3c-common",
"build:highlight": "cd node_modules/highlight.js/; npm install; npm update; node ./tools/build.js -n xml javascript css http markdown xquery; cd ../../",
"build:respec-w3c-common": "./tools/build-w3c-common.js",
"jscs": "jscs --esnext tests tools js/core/markdown.js js/core/utils.js",
"jscs": "jscs --esnext tests tools js/core/markdown.js js/core/utils.js js/w3c/linter.js",
"jscs:fix": "jscs --esnext --fix tests",
"jshint": "jshint karma.conf.js tests tools js/core/markdown.js js/core/utils.js",
"jshint": "jshint karma.conf.js tests tools js/core/markdown.js js/core/utils.js js/w3c/linter.js",
"karma": "karma start --single-run",
"postinstall": "npm run build:highlight",
"prepublish": "npm run snyk-protect",
Expand Down
168 changes: 168 additions & 0 deletions tests/spec/w3c/linter-spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
"use strict";
describe("W3C - Linter", function() {
var linter;
beforeAll(function(done) {
require(["w3c/linter"], function(l) {
linter = l;
done();
});
});
describe("hasPriSecConsiderations", function() {
it("ignores just privacy sections", function() {
var doc = document.implementation.createHTMLDocument("test doc");
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(false);
var elem = doc.createElement("h2");
elem.innerHTML = "the privacy of things";
doc.body.appendChild(elem);
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(false);
});
it("ignores just security sections", function() {
var doc = document.implementation.createHTMLDocument("test doc");
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(false);
var elem = doc.createElement("h2");
elem.innerHTML = "security of things";
doc.body.appendChild(elem);
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(false);
});
it("ignores just considerations sections", function() {
var doc = document.implementation.createHTMLDocument("test doc");
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(false);
var elem = doc.createElement("h2");
elem.innerHTML = "Considerations for other things";
doc.body.appendChild(elem);
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(false);
});
it("finds privacy considerations sections", function() {
var doc = document.implementation.createHTMLDocument("test doc");
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(false);
var elem = doc.createElement("h2");
elem.innerHTML = "Considerations of privacy of things";
doc.body.appendChild(elem);
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(true);
});
it("finds security considerations sections", function() {
var doc = document.implementation.createHTMLDocument("test doc");
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(false);
var elem = doc.createElement("h2");
elem.innerHTML = "Considerations of security of things";
doc.body.appendChild(elem);
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(true);
});
it("finds privacy and security considerations sections, irrespective of order", function() {
var doc = document.implementation.createHTMLDocument("test doc");
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(false);
var elem = doc.createElement("h2");
elem.innerHTML = "Privacy and Security Considerations";
doc.body.appendChild(elem);
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(true);
elem.innerHTML = "Security and Privacy Considerations";
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(true);
elem.innerHTML = "Considerations Security Privacy";
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(true);
});
it("finds privacy and security considerations case insensitive", function() {
var doc = document.implementation.createHTMLDocument("test doc");
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(false);
var elem = doc.createElement("h2");
doc.body.appendChild(elem);
elem.innerHTML = "Privacy and Security Considerations";
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(true);
elem.innerHTML = "Privacy and Security Considerations";
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(true);
elem.innerHTML = "PRIVACY and Security Considerations";
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(true);
elem.innerHTML = "PriVacy and SECURITY Considerations";
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(true);
elem.innerHTML = "PRIVACY AND SECURITY CONSIDERATIONS";
expect(linter.rules.hasPriSecConsiderations(doc)).toEqual(true);
elem.innerHTML = "privacy considerations security";
});
});
describe("findHTTPProps", function() {
it("checks any prop ending with 'URI' (case sensitive)", function() {
var conf = {
"FAIL_uri": "http://fail",
"failURIfail": "http://fail",
"URI": "http://pass",
"charterDisclosureURI": "http://pass",
"URI_FAIL": "http://fail",
"uri_FAIL": "http://fail",
};
var props = linter.rules.findHTTPProps(conf, document.location.href);
expect(props).toEqual(jasmine.arrayContaining(["URI", "charterDisclosureURI"]));
conf.charterDisclosureURI = "https://valid";
conf.URI = "https://valid";
props = linter.rules.findHTTPProps(conf, document.location.href);
expect(props.length).toEqual(0);
});
it("checks for prevED, as special case", function() {
var conf = {
"FAIL_uri": "http://fail",
"failURIfail": "http://fail",
"prevED": "http://pass",
"charterDisclosureURI": "http://pass",
"URI_FAIL": "http://fail",
};
var props = linter.rules.findHTTPProps(conf, document.location.href);
expect(props).toEqual(jasmine.arrayContaining(["prevED", "charterDisclosureURI"]));
conf.prevED = "https://valid-now";
props = linter.rules.findHTTPProps(conf, document.location.href);
expect(props).toEqual(jasmine.arrayContaining(["charterDisclosureURI"]));
});
it("flags well-known props as invalid, when invalid URLs are present", function() {
var conf = {
charterDisclosureURI: "http://invalid",
edDraftURI: "http://invalid",
implementationReportURI: "http://invalid",
previousDiffURI: "http://invalid",
previousMaturityURI: "http://invalid",
previousURI: "http://invalid",
prevRecURI: "http://invalid",
testSuiteURI: "http://invalid",
wgPatentURI: "http://invalid",
wgURI: "http://invalid",
};
var props = linter.rules.findHTTPProps(conf, document.location.href);
expect(props).toEqual(jasmine.arrayContaining([
"charterDisclosureURI",
"edDraftURI",
"implementationReportURI",
"previousDiffURI",
"previousMaturityURI",
"previousURI",
"prevRecURI",
"testSuiteURI",
"wgPatentURI",
"wgURI",
]));
});
it("ignores well-known URIs when they are valid", function() {
var conf = {
charterDisclosureURI: "https://valid.com",
edDraftURI: "https://valid.net",
implementationReportURI: "https://valid.org",
previousDiffURI: "https://valid.net",
previousMaturityURI: "https://valid.org",
previousURI: "https://valid.com",
prevRecURI: "https://valid.example",
testSuiteURI: "https://valid.baz",
wgPatentURI: "https://valid.bar",
wgURI: "https://valid.com",
};
var props = linter.rules.findHTTPProps(conf, document.location.href);
expect(props.length).toEqual(0);
});
it("lints URLs by resolving them as real URLs", function() {
var conf = {
"someRelativeURI": "./foo/bar",
"somePathURI": "/foo/bar",
"someControlURI": "https://valid",
};
var props = linter.rules.findHTTPProps(conf, "http://invalid");
expect(props).toEqual(jasmine.arrayContaining(["someRelativeURI", "somePathURI"]));
conf.someControlURI = "http://invalid";
props = linter.rules.findHTTPProps(conf, "http://valid");
expect(props).toEqual(jasmine.arrayContaining(["someControlURI"]));
});
});
});
1 change: 1 addition & 0 deletions tests/test-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ require.config({
"core/jquery-enhanced": "/base/js/core/jquery-enhanced",
"core/pubsubhub": "/base/js/core/pubsubhub",
"core/utils": "/base/js/core/utils",
"w3c/linter": "/base/js/w3c/linter",
"deps/jquery": "/base/js/deps/jquery",
},
});
Expand Down
1 change: 1 addition & 0 deletions tests/testFiles.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
"spec/w3c/abstract-spec.js",
"spec/w3c/conformance-spec.js",
"spec/w3c/headers-spec.js",
"spec/w3c/linter-spec.js",
"spec/w3c/local-aliases-spec.js",
"spec/w3c/permalinks-spec.js",
"spec/w3c/rdfa-spec.js",
Expand Down

0 comments on commit 10d2429

Please sign in to comment.