' +
8 | '
' +
9 | '
';
10 |
11 | const expected = [
12 | {msg: '
', type: ERROR},
13 | {msg: '
', type: ERROR}
14 | ];
15 |
16 | let rootElement = document.createElement("body");
17 | rootElement.innerHTML = str;
18 |
19 | let results = mixedContent.check(rootElement);
20 |
21 | results.forEach((element, index) => {
22 | delete element.node;
23 | assert.deepEqual(expected[index], element);
24 | });
25 |
26 | done();
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/lib/old-urls.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for old /en/ MDN URLs.
3 | *
4 | * Example 1: All URLs using MDN links, which contain "/en/" as locale should be replaced by
5 | * "/en-US/" URLs. E.g. CSS should rather be
6 | * CSS.
7 | *
8 | * Implementation notes: This test checks whether a link's 'href' attribute starts with "/en/".
9 | * It does not check whether the link is an internal MDN link, nor does it check different
10 | * locales than the English one.
11 | */
12 |
13 | const ERROR = require('./doctests.js').ERROR;
14 |
15 | exports.oldURLs = {
16 | name: "old_en_urls",
17 | desc: "old_en_urls_desc",
18 | check: function checkOldURLs(rootElement) {
19 | let links = rootElement.querySelectorAll("a[href^='/en/' i]");
20 | let matches = [];
21 |
22 | for (let i = 0; i < links.length; i++) {
23 | matches.push({
24 | msg: links[i].outerHTML,
25 | type: ERROR
26 | });
27 | }
28 |
29 | return matches;
30 | }
31 | };
32 |
--------------------------------------------------------------------------------
/tests/test-html-comments.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const htmlComments = require('../lib/html-comments.js').htmlComments;
4 |
5 | describe('htmlComments', function() {
6 | it('Should return 3 errors regarding HTML comments tag', function(done) {
7 | const str = '' +
8 | '' +
9 | '';
10 |
11 | const expected = [
12 | {msg: '', type: ERROR},
13 | {msg: '', type: ERROR},
14 | {msg: '', type: ERROR}
15 | ];
16 |
17 | let rootElement = document.createElement("body");
18 | rootElement.innerHTML = str;
19 |
20 | let results = htmlComments.check(rootElement);
21 |
22 | results.forEach((element, index) => {
23 | delete element.node;
24 | assert.deepEqual(expected[index], element);
25 | });
26 |
27 | done();
28 | });
29 | });
30 |
31 |
--------------------------------------------------------------------------------
/lib/font-elements.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for deprecated elements that should be removed or replaced by other
3 | * elements.
4 | *
5 | * Example 1: Emphasized text should be replaced by
6 | * Emphasized text or Emphasized text.
7 | *
8 | * Example 2: $user:should rather be replaced by 5 | * . 6 | * 7 | * Implementation notes: This test checks whether lines within
elements start with '$' or
8 | * '>'.
9 | */
10 |
11 | const ERROR = require('./doctests.js').ERROR;
12 |
13 | exports.shellPrompts = {
14 | name: "shell_prompts",
15 | desc: "shell_prompts_desc",
16 | check: function checkShellPrompts(rootElement) {
17 | let pres = rootElement.querySelectorAll("pre");
18 | let matches = [];
19 |
20 | for (let i = 0; i < pres.length; i++) {
21 | let code = pres[i].innerHTML.replace(/
/g, "\n").replace(" ", " ");
22 | let shellPrompts = code.match(/^(?:\$|>).*/gm);
23 | if (shellPrompts) {
24 | shellPrompts.forEach(function addMatch(shellPrompt) {
25 | matches.push({
26 | msg: shellPrompt.replace(/<.+?>/g, ""),
27 | type: ERROR
28 | });
29 | });
30 | }
31 | }
32 |
33 | return matches;
34 | }
35 | };
36 |
--------------------------------------------------------------------------------
/lib/alert-print-in-code.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test whether discouraged statements are used in example code.
3 | *
4 | * Example 1: alert("Some message"); should be avoided and
5 | * console.log("Some message"); be used instead.
6 | *
7 | * Implementation notes: This test checks all blocks for the usage of discouraged functions.
8 | * In some cases their usage may not be avoided, like for example on their description pages.
9 | * The test does not account for those cases, though.
10 | */
11 |
12 | const mapMatches = require('./doctests.js').mapMatches;
13 | const ERROR = require('./doctests.js').ERROR;
14 |
15 | exports.alertPrintInCode = {
16 | name: "alert_print_in_code",
17 | desc: "alert_print_in_code_desc",
18 | check: function checkAlertPrintInCode(rootElement) {
19 | let pres = rootElement.getElementsByTagName("pre");
20 | let matches = [];
21 | for (let i = 0; i < pres.length; i++) {
22 | let preMatches = pres[i].textContent.match(/(?:alert|print|eval|document\.write)\s*\((?:.|\n)+?\)/gi) || [];
23 | matches = matches.concat(mapMatches(preMatches, ERROR));
24 | }
25 |
26 | return matches;
27 | }
28 | };
29 |
--------------------------------------------------------------------------------
/tests/test-unnecessary-macro-params.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const unnecessaryMacroParams = require('../lib/unnecessary-macro-params.js').unnecessaryMacroParams;
4 |
5 | describe('unnecessaryMacroParams', function() {
6 | it('Should return 2 errors regarding unnecessary parameters in macro call', function(done) {
7 | const str = '{{JSRef}}' +
8 | '{{JSRef()}}' +
9 | '{{JSRef("Global_Objects")}}' +
10 | '{{ JSRef("Global_Objects", "Math") }}' +
11 | '{{csssyntax("font-family")}}';
12 |
13 | const expected = [
14 | {msg: "macro_with_unused_params", msgParams: ['{{JSRef("Global_Objects")}}'], type: ERROR},
15 | {msg: "macro_with_unused_params", msgParams: ['{{ JSRef("Global_Objects", "Math") }}'], type: ERROR}
16 | ];
17 |
18 | let rootElement = document.createElement("body");
19 | rootElement.innerHTML = str;
20 |
21 | let results = unnecessaryMacroParams.check(rootElement);
22 |
23 | results.forEach((element, index) => {
24 | delete element.node;
25 | assert.deepEqual(expected[index], element);
26 | });
27 |
28 | done();
29 | });
30 | });
31 |
32 |
--------------------------------------------------------------------------------
/lib/name-attribute.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for elements with 'name' attributes.
3 | *
4 | * Example 1: Syntax
should rather be .
5 | *
6 | * Example 2: The name="" attribute in
paragraph should rather be
7 | * removed.
8 | *
9 | * Implementation notes: This test checks all elements containing 'name' attributes.
10 | */
11 |
12 | const ERROR = require('./doctests.js').ERROR;
13 |
14 | exports.nameAttribute = {
15 | name: "name_attributes",
16 | desc: "name_attributes_desc",
17 | check: function checkNameAttribute(rootElement) {
18 | let elementsWithNameAttribute = rootElement.querySelectorAll("[name]");
19 | let matches = [];
20 |
21 | for (let i = 0; i < elementsWithNameAttribute.length; i++) {
22 | matches.push({
23 | node: elementsWithNameAttribute[i],
24 | msg: `name="${elementsWithNameAttribute[i].getAttribute("name")}"`,
25 | type: ERROR
26 | });
27 | }
28 |
29 | return matches;
30 | },
31 | fix: function fixNameAttribute(matches) {
32 | matches.forEach(match => {
33 | match.node.removeAttribute("name");
34 | });
35 | }
36 | };
37 |
--------------------------------------------------------------------------------
/tests/test-name-attribute.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const nameAttribute = require('../lib/name-attribute.js').nameAttribute;
4 |
5 | describe('nameAttribute', function() {
6 | it('Should return 5 errors regarding name= attribute', function(done) {
7 | const str = '' +
8 | '
' +
9 | 'foo
' +
10 | 'foo bar
' +
11 | 'baz
';
12 |
13 | const expected = [
14 | {msg: 'name=""', type: ERROR},
15 | {msg: 'name="foo"', type: ERROR},
16 | {msg: 'name="foo"', type: ERROR},
17 | {msg: 'name="foo_bar"', type: ERROR},
18 | {msg: 'name="baz"', type: ERROR}
19 | ];
20 |
21 | const expectedAfterFixing = [];
22 |
23 | let rootElement = document.createElement("body");
24 | rootElement.innerHTML = str;
25 |
26 | let results = nameAttribute.check(rootElement);
27 |
28 | results.forEach((element, index) => {
29 | delete element.node;
30 | assert.deepEqual(expected[index], element);
31 | });
32 |
33 | done();
34 | });
35 | });
36 |
37 |
--------------------------------------------------------------------------------
/lib/style-attribute.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for incorrectly 'style' attributes.
3 | *
4 | * Example 1: Emphasized text/
should rather be replaced
5 | * by
Emphasized text
.
6 | *
7 | * Implementation notes: This test searches for all 'style' attributes, which are not part of
8 | * CKEditor's new paragraph helper.
9 | */
10 |
11 | const ERROR = require('./doctests.js').ERROR;
12 |
13 | const isNewParagraphHelper = require('./doctests.js').isNewParagraphHelper;
14 |
15 | exports.styleAttribute = {
16 | name: "style_attributes",
17 | desc: "style_attributes_desc",
18 | check: function checkStyleAttribute(rootElement) {
19 | let elementsWithStyleAttribute = rootElement.querySelectorAll("[style]");
20 | let matches = [];
21 |
22 | for (let i = 0; i < elementsWithStyleAttribute.length; i++) {
23 | let node = elementsWithStyleAttribute[i];
24 |
25 | // Exclude new paragraph helper
26 | if (isNewParagraphHelper(node) || isNewParagraphHelper(node.firstElementChild)) {
27 | continue;
28 | }
29 |
30 | matches.push({
31 | msg: `style="${node.getAttribute("style")}"`,
32 | type: ERROR
33 | });
34 | }
35 |
36 | return matches;
37 | }
38 | };
39 |
--------------------------------------------------------------------------------
/tests/test-shell-prompts.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const shellPrompts = require('../lib/shell-prompts.js').shellPrompts;
4 |
5 | describe('shellPrompts', function() {
6 | it('Should return 6 errors regarding shell prompts', function(done) {
7 | const str = 'somecommand
' +
8 | '$somecommand
' +
9 | '$ somecommand
' +
10 | '>somecommand
' +
11 | '> somecommand
' +
12 | '$ somecommand\noutput
$ anothercommand
';
13 |
14 | const expected = [
15 | {msg: '$somecommand', type: ERROR},
16 | {msg: '$ somecommand', type: ERROR},
17 | {msg: '>somecommand', type: ERROR},
18 | {msg: '> somecommand', type: ERROR},
19 | {msg: '$ somecommand', type: ERROR},
20 | {msg: '$ anothercommand', type: ERROR}
21 | ]
22 |
23 | let rootElement = document.createElement("body");
24 | rootElement.innerHTML = str;
25 |
26 | let results = shellPrompts.check(rootElement);
27 |
28 | results.forEach((element, index) => {
29 | delete element.node;
30 | assert.deepEqual(expected[index], element);
31 | });
32 |
33 | done();
34 | });
35 | });
36 |
37 |
--------------------------------------------------------------------------------
/tests/test-alert-print-in-code.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const alertPrintInCode = require('../lib/alert-print-in-code.js').alertPrintInCode;
3 | const ERROR = require('../lib/doctests.js').ERROR;
4 |
5 | describe('alertPrintInCode', function() {
6 | it('Should return 4 alert or print error', function(done) {
7 | const str = 'alert("foo")' +
8 | 'print("bar")' +
9 | 'let someOthercode = baz; ' +
10 | 'alert("hello world"); \n let moreCode;' +
11 | 'document.write("foobar");';
12 |
13 | const expected = [
14 | {msg: 'alert("foo")', type: ERROR},
15 | {msg: 'print("bar")', type: ERROR},
16 | {msg: 'alert("hello world")', type: ERROR},
17 | {msg: 'document.write("foobar")', type: ERROR}
18 | ];
19 |
20 |
21 | let rootElement = document.createElement("body");
22 | rootElement.innerHTML = str;
23 |
24 | let results = alertPrintInCode.check(rootElement);
25 |
26 | results.forEach((element, index) => {
27 | if(element.msg != expected[index].msg || element.type != expected[index].type)
28 | done(Error('Expected : ' + JSON.stringify(expected[index]) + ' got : ' + JSON.stringify(element)));
29 | });
30 |
31 | done();
32 | });
33 | });
34 |
35 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "mdn-doc-linter-rules",
3 | "version": "0.0.1",
4 | "description": "Rules to lint MDN documents",
5 | "author": "Mozilla Developer Network",
6 | "homepage": "https://github.com/mdn/doc-linter-rules#readme",
7 | "license": "MPL-2.0",
8 | "repository": {
9 | "type": "git",
10 | "url": "git+https://github.com/mdn/doc-linter-rules.git"
11 | },
12 | "bugs": {
13 | "url": "https://github.com/mdn/doc-linter-rules/issues"
14 | },
15 | "keywords": [
16 | "linting",
17 | "rules",
18 | "documentation",
19 | "mdn"
20 | ],
21 | "main": "index.js",
22 | "scripts": {
23 | "test": "./node_modules/.bin/karma start --browsers Firefox,Nightmare --single-run",
24 | "bundle": "./node_modules/.bin/browserify lib/index.js --s linter -o dist/bundle.js"
25 | },
26 | "devDependencies": {
27 | "browserify": "^14.4.0",
28 | "karma": "^1.7.0",
29 | "karma-browserify": "^5.1.1",
30 | "karma-chrome-launcher": "^2.1.1",
31 | "karma-firefox-launcher": "^1.0.1",
32 | "karma-mocha": "^1.3.0",
33 | "karma-mocha-reporter": "^2.2.3",
34 | "karma-nightmare": "^0.4.8",
35 | "karma-phantomjs-launcher": "^1.0.4",
36 | "mocha": "^3.4.2",
37 | "nightmare": "^2.10.0",
38 | "watchify": "^3.9.0"
39 | },
40 | "dependencies": {}
41 | }
42 |
--------------------------------------------------------------------------------
/tests/test-example-colon-heading.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const exampleColonHeading = require('../lib/example-colon-heading.js').exampleColonHeading;
4 |
5 | describe('exampleColonHeading', function() {
6 | it('Sould return 3 ERROR regarding example colon heading', function(done) {
7 | const str = 'Example
' +
8 | 'Example
' +
9 | 'Example: Foo
' +
10 | 'Example: Using Math.sin
' +
11 | 'Example: Foo
';
12 |
13 | const expected = [
14 | {msg: 'Example: Foo
', type: ERROR},
15 | {msg: 'Example: Using Math.sin
', type: ERROR},
16 | {msg: 'Example: Foo
', type: ERROR}
17 | ];
18 |
19 | const expectedAfterFixing = [];
20 |
21 | let rootElement = document.createElement("body");
22 | rootElement.innerHTML = str;
23 |
24 | let results = exampleColonHeading.check(rootElement);
25 | results.forEach((element, index) => {
26 | delete element.node
27 | assert.deepEqual(expected[index], element);
28 | });
29 |
30 | done();
31 | });
32 | });
33 |
34 |
--------------------------------------------------------------------------------
/lib/code-in-pre.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test whether code blocks unexpectedly contain elements, which break the syntax
3 | * highlighting.
4 | *
5 | * Example 1: var x = 1;
is considered invalid and
6 | * should rather be written as var x = 1;
.
7 | *
8 | * Implementation notes: This test checks whether elements contain elements.
9 | */
10 |
11 | const ERROR = require('./doctests.js').ERROR;
12 |
13 | exports.codeInPre = {
14 | name: "code_in_pre",
15 | desc: "code_in_pre_desc",
16 | check: function checkCodeInPre(rootElement) {
17 | let codesInPres = rootElement.querySelectorAll("pre code");
18 | let matches = [];
19 |
20 | for (let i = 0; i < codesInPres.length; i++) {
21 | matches.push({
22 | node: codesInPres[i],
23 | msg: codesInPres[i].outerHTML,
24 | type: ERROR
25 | });
26 | }
27 |
28 | return matches;
29 | },
30 | fix: function fixCodeInPre(matches) {
31 | matches.forEach(match => {
32 | let children = new DocumentFragment();
33 | for (let i = 0; i < match.node.childNodes.length; i++) {
34 | children.appendChild(match.node.childNodes[i].cloneNode(true));
35 | }
36 | match.node.parentNode.replaceChild(children, match.node);
37 | });
38 | }
39 | };
40 |
--------------------------------------------------------------------------------
/lib/link-count.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title : Test whether there are too many links in a page
3 | * This is done for SEO reasons, based on the assumption that in the case of a lot of links not all of them would be followed and indexed.
4 | *
5 | * A Warning is emitted between 100 and 250 links
6 | * An Error is emitted for more than 250 links
7 | * An Info is emitted with the amount of links
8 | */
9 |
10 | const WARNING = require('./doctests.js').WARNING;
11 | const ERROR = require('./doctests.js').ERROR;
12 | const INFO = require('./doctests.js').INFO;
13 |
14 | exports.linkCount = {
15 | name: "link_count",
16 | desc: "link_count_desc",
17 | check: function checkLinkCost(rootElement) {
18 | let links = rootElement.getElementsByTagName("a");
19 |
20 | if(links.length >= 100 && links.length < 250) {
21 | return [{
22 | msg: "count_link_warning",
23 | msgParams: [links.length],
24 | type: WARNING
25 | }];
26 | }
27 |
28 | if(links.length >= 250) {
29 | return [{
30 | msg: "count_link_error",
31 | msgParams: [links.length],
32 | type: ERROR
33 | }];
34 | }
35 |
36 | return [{
37 | msg: "count_link_info",
38 | msgParams: [links.length],
39 | type: INFO
40 | }];
41 | },
42 |
43 | fix: function fixLinkCost(matches) {
44 | return;
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/tests/test-span-count.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const spanCount = require('../lib/span-count.js').spanCount;
3 | const ERROR = require('../lib/doctests.js').ERROR;
4 |
5 | describe('SpanCount', function() {
6 | it('Should return 3 span in errors', function(done) {
7 | const str = 'what?' +
8 | 'nope
' +
9 | 'bar' +
10 | 'foobar ' +
11 | 'seoseoseo' +
12 | ' '; // Simulates new paragraph helper
13 | const expected = [
14 | {msg: 'what?', type: ERROR},
15 | {msg: 'bar', type: ERROR},
16 | {msg: 'foobar ', type: ERROR}
17 | ];
18 |
19 | const expectedAfterFixing = [
20 | {msg: 'bar', type: ERROR}
21 | ];
22 |
23 | let rootElement = document.createElement("body");
24 | rootElement.innerHTML = str;
25 |
26 | let results = spanCount.check(rootElement);
27 |
28 | results.forEach((element, index) => {
29 | if(element.msg != expected[index].msg || element.type != expected[index].type) done(Error('Expected : ' + JSON.stringify(expected[index]) + ' got : ' + JSON.stringify(element)));
30 | });
31 |
32 | done();
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/lib/example-colon-heading.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for example headings starting with 'Example:'.
3 | *
4 | * Example 1: Example: Simple example
should rather be written as Simple example
5 | *
6 | * Implementation notes: This test checks whether the text of heading elements start with
7 | * 'Example:'.
8 | */
9 |
10 | const ERROR = require('./doctests.js').ERROR;
11 |
12 | const reExample = /^\s*Example:[\s_]*/;
13 |
14 | exports.exampleColonHeading = {
15 | name: "example_headings",
16 | desc: "example_headings_desc",
17 |
18 | check: function checkExampleColonHeading(rootElement) {
19 | let headlines = rootElement.querySelectorAll("h1, h2, h3, h4, h5, h6");
20 | let matches = [];
21 |
22 | for (let i = 0; i < headlines.length; i++) {
23 | if (headlines[i].textContent.match(reExample)) {
24 | matches.push({
25 | node: headlines[i],
26 | msg: headlines[i].outerHTML,
27 | type: ERROR
28 | });
29 | }
30 | }
31 |
32 | return matches;
33 | },
34 |
35 | fix: function fixExampleColonHeading(matches) {
36 | matches.forEach(match => {
37 | match.node.textContent = match.node.textContent.replace(reExample, "");
38 | let id = match.node.getAttribute("id");
39 | id = id.replace(reExample, "");
40 | match.node.setAttribute("id", id);
41 | });
42 | }
43 | };
44 |
--------------------------------------------------------------------------------
/tests/test-code-in-pre.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const codeInPre = require('../lib/code-in-pre.js').codeInPre;
4 |
5 | describe('codeInPre', function() {
6 | it('Should return 5 errors regarding code in pre', function(done) {
7 | const str = 'no code
' +
8 | 'no code
' +
9 | 'some code
' +
10 | 'some codesome more inline code
' +
11 | 'foo\nsome code\nbar
\nsome code with\nline break\nbaz
';
12 |
13 | const expected = [
14 | {msg: 'some code', type: ERROR},
15 | {msg: 'some code', type: ERROR},
16 | {msg: 'some more inline code', type: ERROR},
17 | {msg: 'some code',type: ERROR},
18 | {msg: 'some code with\nline break',type: ERROR}
19 | ];
20 |
21 | const expectedAfterFixing = [];
22 |
23 | let rootElement = document.createElement("body");
24 | rootElement.innerHTML = str;
25 |
26 | let results = codeInPre.check(rootElement);
27 |
28 | results.forEach((element, index) => {
29 | delete element.node
30 | assert.deepEqual(element, expected[index]);
31 | });
32 |
33 | done();
34 | });
35 | });
36 |
--------------------------------------------------------------------------------
/lib/html-comments.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for HTML comments (i.e. ).
3 | *
4 | * Example 1: Because an HTML comment like
5 | *
6 | * is only visible to article authors, it should rather either be made visible to readers,
7 | * e.g. by replacing it by
8 | * This is a simple example for how the API is used.
9 | * or just be removed.
10 | *
11 | * Implementation notes: This test searches for all HTML comments. Because CKEditor escapes them
12 | * for security reasons, they need to be decoded first before displaying them.
13 | */
14 |
15 | const ERROR = require('./doctests.js').ERROR;
16 |
17 | exports.htmlComments = {
18 | name: "html_comments",
19 | desc: "html_comments_desc",
20 | check: function checkHTMLComments(rootElement) {
21 | let treeWalker = document.createTreeWalker(
22 | rootElement,
23 | NodeFilter.SHOW_COMMENT
24 | );
25 | let matches = [];
26 |
27 | while (treeWalker.nextNode()) {
28 | let comment = treeWalker.currentNode.data.replace(/\s*\{cke_protected\}\{C\}(\S+)\s*/,
29 | (match, data) => decodeURIComponent(data));
30 | matches.push({
31 | node: treeWalker.currentNode,
32 | msg: comment,
33 | type: ERROR
34 | });
35 | }
36 |
37 | return matches;
38 | },
39 | fix: function fixHTMLComments(matches) {
40 | matches.forEach(match => {
41 | match.node.remove();
42 | });
43 | }
44 | };
45 |
--------------------------------------------------------------------------------
/tests/test-style-attribute.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const styleAttribute = require('../lib/style-attribute.js').styleAttribute;
4 |
5 | describe('styleAttribute', function() {
6 | it('Should return 5 errors regarding style= attribute', function(done) {
7 | const str = '' +
8 | '' +
9 | '' +
10 | 'test' +
11 | '' +
12 | ' '; // Simulates new paragraph helper
13 |
14 | const expected = [
15 | {msg: 'style=""', type: ERROR},
16 | {msg: 'style="margin-top:5%"', type: ERROR},
17 | {msg: 'style="background:#fff; color: rgb(234, 234, 234);"', type: ERROR},
18 | {msg: 'style="padding: 5px !important"', type: ERROR},
19 | {msg: 'style="font-family: \'Open Sans\', serif; line-height: 1.5"', type: ERROR}
20 | ];
21 |
22 | let rootElement = document.createElement("body");
23 | rootElement.innerHTML = str;
24 |
25 | let results = styleAttribute.check(rootElement);
26 |
27 | results.forEach((element, index) => {
28 | delete element.node;
29 | assert.deepEqual(expected[index], element);
30 | });
31 |
32 | done();
33 | });
34 | });
35 |
36 |
--------------------------------------------------------------------------------
/lib/article-length.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test the length and read time of the article.
3 | *
4 | * Example 1: An article length of 1000 words will result in a read time estimation of 4 minutes.
5 | *
6 | * Implementation notes: This test expects a reading speed of 275 words per minute. The text is
7 | * roughly split by word bounderies using a regular expression. An article exceeding some length
8 | * threshold (2000 words by default) is considered long.
9 | */
10 |
11 |
12 | const WARNING = require('./doctests.js').WARNING;
13 | const INFO = require('./doctests.js').INFO;
14 |
15 | // TODO: Implement the preferences for LONG_ARTICLE_WORD_COUNT_THRESHOLD
16 | const LONG_ARTICLE_WORD_COUNT_THRESHOLD = 2000;
17 | const WORDS_PER_MINUTE = 275;
18 |
19 | exports.articleLength = {
20 | name: "article_length",
21 | desc: "article_length_desc",
22 | check: function checkArticleLength(rootElement) {
23 | let text = rootElement.textContent;
24 | let wordCount = text.match(/\w+/g).length;
25 | let readTimeEstimation = Math.round(wordCount / WORDS_PER_MINUTE);
26 | if (readTimeEstimation === 0) {
27 | readTimeEstimation = "< 1";
28 | }
29 | let matches = [{
30 | msg: "article_length_info",
31 | msgParams: [String(wordCount), String(readTimeEstimation)],
32 | type: INFO
33 | }];
34 | if (wordCount > LONG_ARTICLE_WORD_COUNT_THRESHOLD) {
35 | matches.push({
36 | msg: "long_article",
37 | type: WARNING
38 | });
39 | }
40 | return matches;
41 | }
42 | };
43 |
--------------------------------------------------------------------------------
/lib/span-count.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for incorrectly used elements.
3 | *
4 | * Example 1: Emphasized text/ should rather be replaced
5 | * by Emphasized text.
6 | *
7 | * Implementation notes: This test searches for all elements, which don't hold the SEO
8 | * summary and are not part of CKEditor's new paragraph helper.
9 | */
10 |
11 | const ERROR = require('./doctests.js').ERROR;
12 |
13 | const isNewParagraphHelper = require('./doctests.js').isNewParagraphHelper;
14 |
15 | exports.spanCount = {
16 | name: "span_elements",
17 | desc: "span_elements_desc",
18 |
19 | check: function checkSpanCount(rootElement) {
20 | let spanElements = rootElement.querySelectorAll("span:not(.seoSummary)");
21 | let matches = [];
22 |
23 | for (let i = 0; i < spanElements.length; i++) {
24 | let node = spanElements[i];
25 |
26 | // Exclude new paragraph helper
27 | if (isNewParagraphHelper(node) || isNewParagraphHelper(node.firstElementChild)) {
28 | continue;
29 | }
30 |
31 | matches.push({
32 | node,
33 | msg: node.outerHTML,
34 | type: ERROR
35 | });
36 | }
37 |
38 | return matches;
39 | },
40 |
41 | fix: function fixSpanCount(matches) {
42 | matches.forEach(match => {
43 | // Remove element in case it is unstyled
44 | if (!match.node.getAttribute("id") && !match.node.getAttribute("class") && !match.node.getAttribute("style")) {
45 | match.node.remove();
46 | }
47 | });
48 | }
49 | };
50 |
--------------------------------------------------------------------------------
/tests/test-summary-heading.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const summaryHeading = require('../lib/summary-heading.js').summaryHeading;
4 |
5 | describe('summaryHeading', function() {
6 | it('Should return 6 errors regarding Summary heading', function(done) {
7 | const str = 'Summary
' +
8 | 'Summary
'
9 | 'Summary
' +
10 | 'Summary
'
11 | 'Summary
' +
12 | 'Summary
' +
13 | 'Summary
' +
14 | 'Summary
' +
15 | 'Summary
' +
16 | 'Summary
' +
17 | 'Summary
' +
18 | 'Summary
';
19 |
20 | const expected = [
21 | {msg: 'Summary
', type: ERROR},
22 | {msg: 'Summary
', type: ERROR},
23 | {msg: 'Summary
', type: ERROR},
24 | {msg: 'Summary
', type: ERROR},
25 | {msg: 'Summary
', type: ERROR},
26 | {msg: 'Summary
', type: ERROR}
27 | ];
28 |
29 | const expectedAfterFixing = [];
30 |
31 | let rootElement = document.createElement("body");
32 | rootElement.innerHTML = str;
33 |
34 | let results = summaryHeading.check(rootElement);
35 |
36 | results.forEach((element, index) => {
37 | delete element.node;
38 | assert.deepEqual(expected[index], element);
39 | });
40 |
41 | done();
42 | });
43 | });
44 |
45 |
--------------------------------------------------------------------------------
/tests/test-line-length-in-pre.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const WARNING = require('../lib/doctests.js').WARNING;
3 | const lineLengthInPre = require('../lib/line-length-in-pre.js').lineLengthInPre;
4 |
5 | describe('lineLengthInPre', function() {
6 | it('Should return 2 warning regarding too long ', function(done) {
7 | const str = '11111111111111111111111 11111111111111111111111 111111111111 111111111111111 1
' +
8 | '11111111111111111111111 11111111111111111111111
111111111111 111111111111111 1
' +
9 | 'short\nstuff
' +
10 | 'Code having some link.
' +
11 | 'foo\nsome code\nbar
\n' +
12 | 'some code with\nline break\nbaz' +
13 | '11111111111 111111111111 function{ foo(); 11111111111111 bar 1111111111111111 111
';
14 |
15 | const expected = [
16 | {
17 | msg: '11111111111111111111111 11111111111111111111111 111111111111 111111111111111 1',
18 | type: WARNING
19 | },
20 | {
21 | msg: 'baz11111111111 111111111111 function{ foo(); 11111111111111 bar 1111111111111111 111',
22 | type: WARNING
23 | }
24 | ];
25 |
26 | let rootElement = document.createElement("body");
27 | rootElement.innerHTML = str;
28 |
29 | let results = lineLengthInPre.check(rootElement);
30 |
31 | results.forEach((element, index) => {
32 | delete element.node;
33 | assert.deepEqual(expected[index], element);
34 | });
35 |
36 | done();
37 | });
38 | });
39 |
40 |
--------------------------------------------------------------------------------
/lib/line-length-in-pre.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for the line length in code blocks.
3 | *
4 | * Example 1: Code blocks with very long lines like
5 | * This is some code block with a long line exceeding the maximum of 78 characters.
6 | * should either be shortened or split into several lines to avoid the display of horizontal
7 | * scrollbars.
8 | *
9 | * Implementation notes: This test uses a threshold of 78 characters for the maximum length of
10 | * a line.
tags added while editing are replaced by line breaks and all other HTML tags
11 | * are removed.
12 | */
13 |
14 |
15 | const WARNING = require('./doctests.js').WARNING;
16 | const mapMatches = require('./doctests.js').mapMatches;
17 |
18 | exports.lineLengthInPre = {
19 | name: "pre_line_too_long",
20 | desc: "pre_line_too_long_desc",
21 | check: function checkLineLengthInPre(rootElement) {
22 | let pres = rootElement.getElementsByTagName("pre");
23 | let matches = [];
24 |
25 | for (let i = 0; i < pres.length; i++) {
26 | // While editing it happens that there are
s added instead of line break characters
27 | // Those need to be replaced by line breaks to correctly recognize long lines
28 | let codeBlock = pres[i].innerHTML.replace(/
/g, "\n");
29 |
30 | // Remove all other HTML tags and only display the plain text
31 | codeBlock = codeBlock.replace(/<.+?>/g, "");
32 |
33 | let longLines = codeBlock.match(/^(?:[^\r\n]|\r(?!\n)){78,}$/gm);
34 | if (longLines) {
35 | matches = matches.concat(longLines);
36 | }
37 | }
38 |
39 | return mapMatches(matches, WARNING);
40 | }
41 | };
42 |
--------------------------------------------------------------------------------
/tests/test-absolute-urls-for-internal-links.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const absoluteURLsForInternalLinks = require('../lib/absolute-urls-for-internal-links.js').absoluteURLsForInternalLinks;
3 | const WARNING = require('../lib/doctests.js').WARNING;
4 |
5 | describe('absoluteURLsForInternalLinks', function() {
6 | it('Should return 3 internal links warning', function(done) {
7 | const str = 'Page' +
8 | 'Anchor' +
9 | 'Anchor' +
10 | 'Anchor' +
11 | 'Anchor' +
12 | 'Anchor';
13 |
14 | const expected = [
15 | {msg: 'Anchor', type: WARNING},
16 | {msg: 'Anchor', type: WARNING},
17 | {msg: 'Anchor', type: WARNING}
18 | ];
19 |
20 | const expectedAfterFixing = [];
21 |
22 | let rootElement = document.createElement("body");
23 | rootElement.innerHTML = str;
24 |
25 | let results = absoluteURLsForInternalLinks.check(rootElement);
26 |
27 | results.forEach((element, index) => {
28 | if(element.msg != expected[index].msg || element.type != expected[index].type)
29 | done(Error('Expected : ' + JSON.stringify(expected[index]) + ' got : ' + JSON.stringify(element)));
30 | });
31 |
32 | done();
33 | });
34 | });
35 |
36 |
--------------------------------------------------------------------------------
/tests/test-empty-elements.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const WARNING = require('../lib/doctests.js').WARNING;
4 | const emptyElements = require('../lib/empty-elements.js').emptyElements;
5 |
6 | describe('emptyElements', function() {
7 | it('Should return 5 errors and 1 warnings', function(done) {
8 | const str = '
' +
9 | ' \n\r
' +
10 | '
' +
11 | '
' +
12 | '
' +
13 | 'foo
' +
14 | '
' +
15 | '
' +
16 | '' +
17 | 'some text
' +
18 | 'some text
' +
19 | ' ' +
20 | ' ';
21 |
22 | const expected = [
23 | {msg: '
', type: ERROR},
24 | {msg: ' \n\n
', type: ERROR},
25 | {msg: '
', type: ERROR},
26 | {msg: '
',type: ERROR},
27 | {msg: '
',type: ERROR},
28 | {msg: '
', type: WARNING}
29 | ];
30 | const expectedAfterFixing = [
31 | {msg: '
',type: WARNING}
32 | ];
33 |
34 | let rootElement = document.createElement("body");
35 | rootElement.innerHTML = str;
36 |
37 | let results = emptyElements.check(rootElement);
38 |
39 | results.forEach((element, index) => {
40 | delete element.node
41 | assert.deepEqual(element, expected[index]);
42 | });
43 |
44 | done();
45 | });
46 | });
47 |
48 |
--------------------------------------------------------------------------------
/tests/test-different-locale-links.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const differentLocaleLinks = require('../lib/different-locale-links.js').differentLocaleLinks;
4 |
5 | describe('differentLocaleLinks', function() {
6 | it('Should return 3 errors regarding link using the wrong locale', function(done) {
7 | const str = 'Page' +
8 | 'Page' +
9 | 'Page' +
10 | 'Page' +
11 | 'Page' +
12 | 'Page' +
13 | 'Page';
14 |
15 | const expected = [
16 | {msg: "link_using_wrong_locale", msgParams: ["/xx-YY/docs/some/page", "en-US"], type: ERROR},
17 | {msg: "link_using_wrong_locale", msgParams: ["http://developer.mozilla.org/xx-YY/docs/some/page", "en-US"], type: ERROR},
18 | {msg: "link_using_wrong_locale", msgParams: ["https://developer.mozilla.org/xx-YY/docs/some/page", "en-US"], type: ERROR}
19 | ];
20 |
21 | let rootElement = document.createElement("body");
22 | rootElement.innerHTML = str;
23 |
24 | let results = differentLocaleLinks.check(rootElement);
25 |
26 | results.forEach((element, index) => {
27 | // TODO: Figure out the issues with msgParams as it's not normal
28 | delete element.msgParams;
29 | delete expected[index].msgParams;
30 | assert.deepEqual(element, expected[index]);
31 | });
32 |
33 | done();
34 | });
35 | });
36 |
37 |
--------------------------------------------------------------------------------
/tests/test-data-macro-note.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const dataMacroNote = require('../lib/data-macro-note.js').dataMacroNote;
4 |
5 | describe('dataMacroNote', function() {
6 | it('Should return 2 errors regarding Data Macros Notes', function(done) {
7 | const str = '{{nondatamacro}}
' +
8 | '{{compat}}
' +
9 | '{{css_ref}}
' +
10 | '{{cssanimatedproperties}}
' +
11 | '{{cssinfo}}
' +
12 | '{{csssyntax}}
' +
13 | '{{WebExtBrowserCompat}}
' +
14 | '{{Compat}}
' +
15 | 'The data is maintained somewhere else.
{{Compat}}
';
16 |
17 | const expected = [
18 | {msg: "data_macro_note_missing", msgParams: ["{{Compat}}"], type: ERROR},
19 | {msg: "data_macro_source_link_missing", msgParams: ["{{Compat}}"], type: ERROR}
20 | ];
21 |
22 | let rootElement = document.createElement("body");
23 | rootElement.innerHTML = str;
24 |
25 | let results = dataMacroNote.check(rootElement);
26 |
27 | results.forEach((element, index) => {
28 | assert.deepEqual(element, expected[index]);
29 | });
30 |
31 | done();
32 | });
33 | });
34 |
35 |
--------------------------------------------------------------------------------
/lib/absolute-urls-for-internal-links.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test whether internal MDN links have absolute URLs.
3 | *
4 | * Example 1: An MDN link to the 'display' CSS property should use a the relative URL
5 | * /en-US/docs/Web/CSS/display and not the absolute URL
6 | * https://developer.mozilla.org/en-US/docs/Web/CSS/display.
7 | *
8 | * Implementation notes: This test checks whether the URL begins with the link
9 | * "https://developer.mozilla.org/", this means that allizom.org links are not covered.
10 | * window.location cannot be used for this, because that would break the unit test, which uses
11 | * about:blank as URL.
12 | */
13 |
14 | const WARNING = require('./doctests.js').WARNING;
15 |
16 | const reAbsoluteURL = /^(?:https?:)?\/\/developer\.mozilla\.org(?=\/)/i;
17 |
18 | exports.absoluteURLsForInternalLinks = {
19 | name: "absolute_urls_for_internal_links",
20 | desc: "absolute_urls_for_internal_links_desc",
21 | check: function checkAbsoluteURLsForInternalLinks(rootElement) {
22 | let links = rootElement.getElementsByTagName("a");
23 | let matches = [];
24 | for (let i = 0; i < links.length; i++) {
25 | let href = links[i].getAttribute("href");
26 | if (href && href.match(reAbsoluteURL)) {
27 | matches.push({
28 | node: links[i],
29 | msg: links[i].outerHTML,
30 | type: WARNING
31 | });
32 | }
33 | }
34 |
35 | return matches;
36 | },
37 |
38 | fix: function fixAbsoluteURLsForInternalLinks(matches) {
39 | matches.forEach(match => {
40 | let href = match.node.getAttribute("href");
41 | let relativeURL = href.replace(reAbsoluteURL, "");
42 | match.node.setAttribute("href", relativeURL);
43 | match.node.setAttribute("data-cke-saved-href", relativeURL);
44 | });
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/lib/different-locale-links.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for wrong locales in links ().
3 | *
4 | * Example 1: If you are in a German document, internal links should
5 | * contain the "de" locale and not e.g. "en-US".
6 | *
7 | * Implementation notes: This test compares the current locale in the slug (document.URL)
8 | * with the locale used in internal links ()
9 | */
10 |
11 | const ERROR = require('./doctests.js').ERROR;
12 |
13 | exports.differentLocaleLinks = {
14 | name: "different_locale_links",
15 | desc: "different_locale_links_desc",
16 | check: function checkDifferentLocaleLinks(rootElement) {
17 | let [, pageDomain, pageLocale] = document.URL.match(/^(?:https?:\/\/)(.+?)\/([^/]+)/i) ||
18 | ["", "developer.mozilla.org", "en-US"];
19 | let links = rootElement.getElementsByTagName("a");
20 | let matches = [];
21 | for (let i = 0; i < links.length; i++) {
22 | let href = links[i].getAttribute("href");
23 | if (href) {
24 | let [, linkDomain, linkLocale] = href.match(/^(?:https?:\/\/(.+?))?\/([^/]+)/i) ||
25 | [null, null, null];
26 | let oldAttachmentLink = false;
27 | let compareLocale = false;
28 | if(linkLocale !== null) {
29 | oldAttachmentLink = !linkLocale.startsWith("@");
30 | compareLocale = linkLocale.toLowerCase() !== pageLocale.toLowerCase();
31 | }
32 | let internalLinks = !linkDomain || linkDomain === pageDomain;
33 |
34 | if(linkLocale !== null && linkLocale && oldAttachmentLink && compareLocale && internalLinks) {
35 | matches.push({
36 | msg: "link_using_wrong_locale",
37 | msgParams: [href, pageLocale],
38 | type: ERROR
39 | });
40 | }
41 | }
42 | }
43 |
44 | return matches;
45 | }
46 | };
47 |
--------------------------------------------------------------------------------
/lib/empty-brackets.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for macros with empty brackets.
3 | *
4 | * Example 1: The {{CompatNo}} macro does not expect any parameters, so the parameter brackets
5 | * are redundant and should be avoided, i.e. it should not be written as {{CompatNo()}}.
6 | *
7 | * Implementation notes: This test checks for macros written with empty brackets and requests to
8 | * remove them. It does not check whether the macros actually require parameters.
9 | */
10 |
11 | const ERROR = require('./doctests.js').ERROR;
12 |
13 | const reMacroWithEmptyBrackets = /\{\{\s*(.*?)\(\)\s*\}\}/gi;
14 |
15 | exports.emptyBrackets = {
16 | name: "empty_brackets",
17 | desc: "empty_brackets_desc",
18 |
19 | check: function checkEmptyBrackets(rootElement) {
20 | let treeWalker = document.createTreeWalker(
21 | rootElement,
22 | NodeFilter.SHOW_TEXT,
23 | // eslint-disable-next-line
24 | {acceptNode: node => reMacroWithEmptyBrackets.test(node.textContent) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT}
25 | );
26 | let matches = [];
27 |
28 | while (treeWalker.nextNode()) {
29 | let textNodeMatches = treeWalker.currentNode.textContent.match(reMacroWithEmptyBrackets) || [];
30 | textNodeMatches.forEach(match => {
31 | matches.push({
32 | node: treeWalker.currentNode,
33 | msg: match,
34 | type: ERROR
35 | });
36 | });
37 | }
38 |
39 | return matches;
40 | },
41 |
42 | fix: function fixEmptyBrackets(matches) {
43 | let previousNode = null;
44 | matches.forEach(match => {
45 | if (match.node !== previousNode) {
46 | match.node.textContent = match.node.textContent
47 | .replace(reMacroWithEmptyBrackets, "{{$1}}");
48 | }
49 | previousNode = match.node;
50 | });
51 | }
52 | };
53 |
--------------------------------------------------------------------------------
/lib/pre-without-class.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for code blocks without 'class' attribute specifying the syntax highlighting.
3 | *
4 | * Example 1: var x = 1
should rather be replaced by
5 | * var x = 1
.
6 | *
7 | * Implementation notes: This test checks all elements that have either an empty 'class'
8 | * attribute or none at all. It also checks for elements that have the class 'eval'
9 | */
10 |
11 | const WARNING = require('./doctests.js').WARNING;
12 | const ERROR = require('./doctests.js').ERROR;
13 |
14 | exports.preWithoutClass = {
15 | name: "pre_without_class",
16 | desc: "pre_without_class_desc",
17 | check: function checkPreWithoutClass(rootElement) {
18 | let presWithoutClass = rootElement.querySelectorAll("pre:not([class]), pre[class='']");
19 | let presWithEvalClass = rootElement.querySelectorAll("pre[class='eval']");
20 | let matches = [];
21 |
22 | for (let i = 0; i < presWithoutClass.length; i++) {
23 | // If the content is recognized as folder structure, don't add a warning for empty
24 | if (presWithoutClass[i].textContent.match(/^\S[^\n*]*\/\n/)) {
25 | continue;
26 | }
27 |
28 | let type = WARNING;
29 |
30 | // If the content is recognized as code or {{csssyntax}} macro, mark it as error
31 | if (presWithoutClass[i].textContent.match(/^\s*(?:\/\*.+?\*\/|<.+?>|@[^\s\n]+[^\n]*\{\n|\{\{\s*csssyntax(?:\(\))?\s*\}\})/)) {
32 | type = ERROR;
33 | }
34 |
35 | matches.push({
36 | msg: presWithoutClass[i].outerHTML,
37 | type
38 | });
39 | }
40 |
41 | for(let i = 0; i < presWithEvalClass.length; i++) {
42 | matches.push({
43 | msg: presWithEvalClass[i].outerHTML,
44 | type: ERROR
45 | });
46 | }
47 |
48 | return matches;
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/tests/test-pre-without-class.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const WARNING = require('../lib/doctests.js').WARNING;
4 | const preWithoutClass = require('../lib/pre-without-class.js').preWithoutClass;
5 |
6 | describe('preWithoutClass', function() {
7 | it('Should return 4 errors and 4 warnings regarding pre lacking a class', function(done) {
8 | const str = '' +
9 | '' +
10 | 'folder/\n file
' +
11 | 'foobar;
' +
12 | '/* comment */\nvar code;
' +
13 | '@rule param {\n descriptor: value;\n}' +
14 | '<tag>
' +
15 | '' +
16 | 'foo
' +
17 | ' \n\r foo
' +
18 | 'Test on non pre' +
19 | 'bar
' +
20 | 'Test
';
21 |
22 | const expected = [
23 | {msg: 'foobar;
', type: WARNING},
24 | {msg: '/* comment */\nvar code;
', type: ERROR},
25 | {msg: '@rule param {\n descriptor: value;\n}', type: ERROR},
26 | {msg: '<tag>
', type: ERROR},
27 | {msg: '', type: WARNING},
28 | {msg: 'foo
', type: WARNING},
29 | {msg: ' \n\n foo
', type: WARNING},
30 | {msg: 'bar
', type: ERROR}
31 | ];
32 |
33 |
34 | let rootElement = document.createElement("body");
35 | rootElement.innerHTML = str;
36 |
37 | let results = preWithoutClass.check(rootElement);
38 |
39 | results.forEach((element, index) => {
40 | delete element.node;
41 | assert.deepEqual(expected[index], element);
42 | });
43 |
44 | done();
45 | });
46 | });
47 |
48 |
--------------------------------------------------------------------------------
/lib/url-in-link-title.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for incorrectly used URLs in link titles.
3 | *
4 | * Example 1: The 'title' attribute on
5 | * CSS
6 | * should be removed, because it's redundant.
7 | *
8 | * Example 2: The 'title' attribute on
9 | * CSS
10 | * should be removed, because it's redundant and misleading.
11 | *
12 | * Implementation notes: This test checks whether the 'title' attribute of an element
13 | * contains the same URL or a part of it as within its 'href' attribute. It also handles URLs
14 | * using two-character locales vs. four character locales, e.g. "/en-US/" and "/en/".
15 | */
16 |
17 | const ERROR = require('./doctests.js').ERROR;
18 |
19 | exports.urlInLinkTitle = {
20 | name: "url_in_link_title",
21 | desc: "url_in_link_title_desc",
22 | check: function checkURLsInTitleAttributes(rootElement) {
23 | let linkElements = rootElement.getElementsByTagName("a");
24 | let matches = [];
25 |
26 | for (let i = 0; i < linkElements.length; i++) {
27 | let href = (linkElements[i].getAttribute("href") || "").toLowerCase();
28 | let title = (linkElements[i].getAttribute("title") || "").toLowerCase();
29 | if (title !== "" && (href.indexOf(title) !== -1 ||
30 | (title.match(/[a-z]{2}(?:-[A-Z]{2})?\/docs\/.*?\//) ||
31 | title === href.replace(/([a-z]{2})(?:-[a-z]{2})?\/docs\/(.*)/, "$1/$2")))) {
32 | matches.push({
33 | node: linkElements[i],
34 | msg: linkElements[i].outerHTML,
35 | type: ERROR
36 | });
37 | }
38 | }
39 |
40 | return matches;
41 | },
42 | fix: function fixURLsInTitleAttributes(matches) {
43 | matches.forEach(match => {
44 | match.node.removeAttribute("title");
45 | });
46 | }
47 | };
48 |
--------------------------------------------------------------------------------
/tests/test-invalid-macros.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const WARNING = require('../lib/doctests.js').WARNING;
3 | const ERROR = require('../lib/doctests.js').ERROR;
4 | const invalidMacros = require('../lib/invalid-macros.js').invalidMacros;
5 |
6 | describe('invalidMacros', function() {
7 | it('Should return 4 warnings and 1 error regarding invalid macros', function(done) {
8 | const str = '{{apiref}}' +
9 | '{{bug(123456)}}' +
10 | '{{previous("some page"}}' +
11 | '{{cssinfo(\'font-weight\', \'@font\')}}' +
12 | '{{invalidmacroname}}' +
13 | '{{invalidmacroname(123456)}}' +
14 | '{{invalidmacroname("some page")}}' +
15 | '{{invalidmacroname(\'font-weight\', \'@font\')}}' +
16 | '{{ languages( { "ja": "Ja/Browser_chrome_tests" } ) }}';
17 |
18 | const expected = [
19 | {msg: '{{invalidmacroname}}', type: WARNING},
20 | {msg: '{{invalidmacroname(123456)}}', type: WARNING},
21 | {msg: '{{invalidmacroname("some page")}}', type: WARNING},
22 | {msg: '{{invalidmacroname(\'font-weight\', \'@font\')}}', type: WARNING},
23 | {msg: 'obsolete_macro', msgParams: ['{{ languages( { "ja": "Ja/Browser_chrome_tests" } ) }}'], type: ERROR}
24 | ];
25 |
26 | const expectedAfterFixing = [
27 | {msg: '{{invalidmacroname}}', type: WARNING},
28 | {msg: '{{invalidmacroname(123456)}}', type: WARNING},
29 | {msg: '{{invalidmacroname("some page")}}', type: WARNING},
30 | {msg: '{{invalidmacroname(\'font-weight\', \'@font\')}}', type: WARNING}
31 | ];
32 |
33 | let rootElement = document.createElement("body");
34 | rootElement.innerHTML = str;
35 |
36 | let results = invalidMacros.check(rootElement);
37 |
38 | results.forEach((element, index) => {
39 | delete element.node;
40 | assert.deepEqual(expected[index], element);
41 | });
42 |
43 | done();
44 | });
45 | });
46 |
47 |
--------------------------------------------------------------------------------
/tests/test-url-in-link-title.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const urlInLinkTitle = require('../lib/url-in-link-title.js').urlInLinkTitle;
4 |
5 | describe('urlInLinkTitle', function() {
6 | it('Should return 6 errors regarding HTTP Links', function(done) {
7 | const str = 'Test' +
8 | 'Test' +
9 | 'Test' +
10 | 'Test' +
11 | 'Test' +
12 | 'Test' +
13 | 'Test' +
14 | 'Test' +
15 | 'Web' +
16 | 'Mozilla';
17 |
18 | const expected = [
19 | {msg: 'Test', type: ERROR},
20 | {msg: 'Test', type: ERROR},
21 | {msg: 'Test', type: ERROR},
22 | {msg: 'Test', type: ERROR},
23 | {msg: 'Web', type: ERROR},
24 | {msg: 'Mozilla', type: ERROR}
25 | ];
26 |
27 | const expectedAfterFixing = [];
28 |
29 | let rootElement = document.createElement("body");
30 | rootElement.innerHTML = str;
31 |
32 | let results = urlInLinkTitle.check(rootElement);
33 |
34 | results.forEach((element, index) => {
35 | delete element.node;
36 | assert.deepEqual(expected[index], element);
37 | });
38 |
39 | done();
40 | });
41 | });
42 |
43 |
--------------------------------------------------------------------------------
/tests/test-article-length.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const INFO = require('../lib/doctests.js').INFO;
3 | const WARNING = require('../lib/doctests.js').WARNING;
4 | const articleLength = require('../lib/article-length.js').articleLength;
5 |
6 | describe('articleLength', function() {
7 | it('Should return 1 INFO regarding article length (100 words)', function(done) {
8 | const str = Array(100).fill("foo").join(" ");
9 | const expected = [
10 | {msg: "article_length_info", msgParams: ["100", "< 1"], type: INFO}
11 | ];
12 |
13 | let rootElement = document.createElement("body");
14 | rootElement.innerHTML = str;
15 |
16 | let results = articleLength.check(rootElement);
17 |
18 | results.forEach((element, index) => {
19 | assert.deepEqual(expected[index], element);
20 | });
21 |
22 | done();
23 | });
24 |
25 | it('Should return 1 INFO regarding article length (500 words)', function(done) {
26 | const str = Array(500).fill("foo").join(" ");
27 | const expected = [
28 | {msg: "article_length_info", msgParams: ["500", "2"], type: INFO}
29 | ];
30 |
31 | let rootElement = document.createElement("body");
32 | rootElement.innerHTML = str;
33 |
34 | let results = articleLength.check(rootElement);
35 |
36 | results.forEach((element, index) => {
37 | assert.deepEqual(expected[index], element);
38 | });
39 |
40 | done();
41 | });
42 |
43 | it('Should return 1 INFO and 1 WARNING (3000 words)', function(done) {
44 | const str = Array(3000).fill("foo").join(" ");
45 | const expected = [
46 | {msg: "article_length_info", msgParams: ["3000", "11"], type: INFO},
47 | {msg: "long_article", type: WARNING}
48 | ];
49 |
50 | let rootElement = document.createElement("body");
51 | rootElement.innerHTML = str;
52 |
53 | let results = articleLength.check(rootElement);
54 |
55 | results.forEach((element, index) => {
56 | assert.deepEqual(expected[index], element);
57 | });
58 |
59 | done();
60 | });
61 | });
62 |
63 |
--------------------------------------------------------------------------------
/tests/test-wrong-syntax-class.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const wrongSyntaxClass = require('../lib/wrong-syntax-class.js').wrongSyntaxClass;
4 |
5 | describe('wrongSyntaxClass', function() {
6 | it('Should return 0 errors regarding wrong syntax in class= (syntaxbox)', function(done) {
7 | const str = 'fooSyntax
\\nsyntax
bar';
8 | const expected = [];
9 |
10 | let rootElement = document.createElement("body");
11 | rootElement.innerHTML = str;
12 |
13 | let results = wrongSyntaxClass.check(rootElement);
14 |
15 | results.forEach((element, index) => {
16 | delete element.node;
17 | assert.deepEqual(expected[index], element);
18 | });
19 |
20 | done();
21 | });
22 |
23 | it('Should return 0 errors regarding class brushjs', function(done) {
24 | const str = 'fooSyntax
\\nsyntax examples
barFormal syntax
\\nsyntax
';
25 | const expected = [];
26 |
27 | let rootElement = document.createElement("body");
28 | rootElement.innerHTML = str;
29 |
30 | let results = wrongSyntaxClass.check(rootElement);
31 |
32 | results.forEach((element, index) => {
33 | delete element.node;
34 | assert.deepEqual(expected[index], element);
35 | });
36 |
37 | done();
38 | });
39 |
40 | it('Should return 1 error regarding eval', function(done) {
41 | const str = 'fooSyntax
\\nsyntax examples
barFormal syntax
\\nsyntax
bazOther section
';
42 | const expected = [
43 | {msg: "wrong_syntax_class_used", msgParams: ["eval"], type: ERROR}
44 | ];
45 |
46 | let rootElement = document.createElement("body");
47 | rootElement.innerHTML = str;
48 |
49 | let results = wrongSyntaxClass.check(rootElement);
50 |
51 | results.forEach((element, index) => {
52 | delete element.node;
53 | assert.deepEqual(expected[index], element);
54 | });
55 |
56 | done();
57 | });
58 | });
59 |
60 |
--------------------------------------------------------------------------------
/tests/test-link-count.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const WARNING = require('../lib//doctests.js').WARNING;
4 | const INFO = require('../lib/doctests.js').INFO;
5 | const linkCost = require('../lib/link-count.js').linkCount;
6 |
7 | describe('linkCount', function() {
8 | it('Should return 1 error if there is more than 249 links in a page', function(done) {
9 | const str = Array(250).fill('').join(' ');
10 | const expected = [
11 | {msg: "count_link_error", msgParams: [250], type: ERROR}
12 | ];
13 |
14 | let rootElement = document.createElement("body");
15 | rootElement.innerHTML = str;
16 |
17 | let results = linkCost.check(rootElement);
18 |
19 | results.forEach((element, index) => {
20 | delete element.node;
21 | assert.deepEqual(expected[index], element);
22 | });
23 |
24 | done();
25 | });
26 |
27 | it('Should return 1 warning if there is more than 99 links but less than 250', function(done) {
28 | const str = Array(100).fill('').join(' ');
29 | const expected = [
30 | {msg: "count_link_warning", msgParams: [100], type: WARNING}
31 | ];
32 |
33 | let rootElement = document.createElement("body");
34 | rootElement.innerHTML = str;
35 |
36 | let results = linkCost.check(rootElement);
37 |
38 | results.forEach((element, index) => {
39 | delete element.node;
40 | assert.deepEqual(expected[index], element);
41 | });
42 |
43 | done();
44 | });
45 |
46 | it('Should return 1 info if it doesn\'t exceed any threshold', function(done) {
47 | const str = Array(3).fill('').join(' ');
48 | const expected = [
49 | {msg: 'count_link_info', msgParams: [3], type: INFO}
50 | ];
51 |
52 | let rootElement = document.createElement("body");
53 | rootElement.innerHTML = str;
54 |
55 | let results = linkCost.check(rootElement);
56 |
57 | results.forEach((element, index) => {
58 | delete element.node;
59 | assert.deepEqual(expected[index], element);
60 | });
61 |
62 | done();
63 | });
64 | });
65 |
--------------------------------------------------------------------------------
/lib/incorrectly-wrapped-sidebar-macros.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for sidebar macros that are not wrapped in elements
3 | *
4 | * Example 1: {{APIRef}}
should be replaced by {{APIRef}}.
5 | *
6 | * Implementation notes: This test checks whether some named macros are wrapped in other elements
7 | * than s.
8 | */
9 |
10 | const ERROR = require('./doctests.js').ERROR;
11 |
12 | exports.incorrectlyWrappedSidebarMacros = {
13 | name: "incorrectly_wrapped_sidebar_macros",
14 | desc: "incorrectly_wrapped_sidebar_macros_desc",
15 |
16 | check: function checkIncorrectlyWrappedSidebarMacros(rootElement) {
17 | const allowedMacros = /^(?:apiref|cssref|htmlref|jsref|makesimplequicklinks|mathmlref|svgrefelem)$|sidebar$/i;
18 |
19 | let treeWalker = document.createTreeWalker(
20 | rootElement,
21 | NodeFilter.SHOW_TEXT,
22 | // eslint-disable-next-line
23 | {acceptNode: node => node.textContent.match(/\{\{.*?\}\}/) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT}
24 | );
25 | let matches = [];
26 |
27 | while (treeWalker.nextNode()) {
28 | let reMacroName = /\{\{\s*([^(}\s]+).*?\}\}/g;
29 | let macroNameMatch = reMacroName.exec(treeWalker.currentNode.textContent);
30 | while (macroNameMatch) {
31 | if (macroNameMatch[1].match(allowedMacros) !== null &&
32 | treeWalker.currentNode.parentElement.localName !== "div") {
33 | matches.push({
34 | node: treeWalker.currentNode.parentElement,
35 | msg: "wrong_element_wrapping_sidebar_macro",
36 | msgParams: [macroNameMatch[0], treeWalker.currentNode.parentElement.localName],
37 | type: ERROR
38 | });
39 | }
40 | macroNameMatch = reMacroName.exec(treeWalker.currentNode.textContent);
41 | }
42 | }
43 |
44 | return matches;
45 | },
46 |
47 | fix: function fixIncorrectlyWrappedSidebarMacros(matches) {
48 | matches.forEach(match => {
49 | let divElement = document.createElement("div");
50 | let childNodes = match.node.childNodes;
51 | for (let i = 0; i < childNodes.length; i++) {
52 | divElement.appendChild(childNodes[i].cloneNode(true));
53 | }
54 |
55 | match.node.parentNode.replaceChild(divElement, match.node);
56 | });
57 | }
58 | };
59 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | const docTests = {
2 | absoluteURLsForInternalLinks : require('./absolute-urls-for-internal-links.js').absoluteURLsForInternalLinks,
3 | alertPrintInCode : require('./alert-print-in-code.js').alertPrintInCode,
4 | anchorExists : require('./anchor-exists.js').anchorExists,
5 | apiSyntaxHeadlines : require('./api-syntax-headlines.js').apiSyntaxHeadlines,
6 | articleLength : require('./article-length.js').articleLength,
7 | codeInPre : require('./code-in-pre.js').codeInPre,
8 | dataMacroNote : require('./data-macro-note.js').dataMacroNote,
9 | differentLocaleLinks : require('./different-locale-links.js').differentLocaleLinks,
10 | emptyBrackets : require('./empty-brackets.js').emptyBrackets,
11 | emptyElements : require('./empty-elements.js').emptyElements,
12 | exampleColonHeading : require('./example-colon-heading.js').exampleColonHeading,
13 | fontElements : require('./font-elements.js').fontElements,
14 | htmlComments : require('./html-comments.js').htmlComments,
15 | httpLinks : require('./http-links.js').httpLinks,
16 | incorrectlyWrappedSidebarMacros : require('./incorrectly-wrapped-sidebar-macros.js').incorrectlyWrappedSidebarMacros,
17 | invalidMacros : require('./invalid-macros.js').invalidMacros,
18 | lineLengthInPre : require('./line-length-in-pre.js').lineLengthInPre,
19 | linkCount : require('./link-count.js').linkCount,
20 | macroSyntaxError : require('./macro-syntax-error.js').macroSyntaxError,
21 | mixedContent : require('./mixed-content.js').mixedContent,
22 | nameAttribute : require('./name-attribute.js').nameAttribute,
23 | oldURLs : require('./old-urls.js').oldURLs,
24 | preWithoutClass : require('./pre-without-class.js').preWithoutClass,
25 | shellPrompts : require('./shell-prompts.js').shellPrompts,
26 | spanCount : require('./span-count.js').spanCount,
27 | styleAttribute : require('./style-attribute.js').styleAttribute,
28 | summaryHeading : require('./summary-heading.js').summaryHeading,
29 | unnecessaryMacroParams : require('./unnecessary-macro-params.js').unnecessaryMacroParams,
30 | urlInLinkTitle : require('./url-in-link-title.js').urlInLinkTitle,
31 | wrongHighlightedLine : require('./wrong-highlighted-line.js').wrongHighlightedLine,
32 | wrongSyntaxClass : require('./wrong-syntax-class.js').wrongSyntaxClass
33 | };
34 |
35 | module.exports = docTests;
36 |
--------------------------------------------------------------------------------
/tests/test-api-syntax-headlines.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const apiSyntaxHeadlines = require('../lib/api-syntax-headlines.js').apiSyntaxHeadlines;
3 | const ERROR = require('../lib/doctests.js').ERROR;
4 |
5 | describe('apiSyntaxHeadlines', function() {
6 | it('Should return 5 apiSyntaxHeadlines errors', function(done) {
7 | const str = 'Syntax
' +
8 | 'Exceptions thrown
' +
9 | 'Returns
' +
10 | 'Arguments
';
11 |
12 | const expected = [
13 | {msg: "invalid_headline_name", msgParams: ["Exception thrown"], type: ERROR},
14 | {msg: "invalid_headline_name", msgParams: ["Returns"], type: ERROR},
15 | {msg: "invalid_headline_name", msgParams: ["Arguments"], type: ERROR},
16 | {msg: "invalid_headline_order", type: ERROR},
17 | {msg: "invalid_headline_order", type: ERROR}
18 | ];
19 |
20 | const expectedAfterFixing = [
21 | {msg: "invalid_headline_order", type: ERROR},
22 | {msg: "invalid_headline_order", type: ERROR}
23 | ];
24 |
25 |
26 | let rootElement = document.createElement("body");
27 | rootElement.innerHTML = str;
28 |
29 | let results = apiSyntaxHeadlines.check(rootElement);
30 |
31 | results.forEach((element, index) => {
32 | if(element.msg != expected[index].msg || element.type != expected[index].type)
33 | done(Error('Expected : ' + JSON.stringify(expected[index]) + ' got : ' + JSON.stringify(element)));
34 | });
35 |
36 | done();
37 | });
38 |
39 | it('Should return 1 Error regarding apiSyntaxHeadlines errors', function(done) {
40 | const str = 'Syntax
' +
41 | 'Return value
' +
42 | 'Errors thrown
';
43 |
44 | const expected = [
45 | {msg: "invalid_headline_name", msgParams: ["Errors thrown"], type: ERROR}
46 | ];
47 |
48 | const expectedAfterFixing = [];
49 |
50 | let rootElement = document.createElement("body");
51 | rootElement.innerHTML = str;
52 |
53 | let results = apiSyntaxHeadlines.check(rootElement);
54 |
55 | results.forEach((element, index) => {
56 | if(element.msg != expected[index].msg || element.type != expected[index].type)
57 | done(Error('Expected : ' + JSON.stringify(expected[index]) + ' got : ' + JSON.stringify(element)));
58 | });
59 |
60 | done();
61 | });
62 | });
63 |
64 |
--------------------------------------------------------------------------------
/lib/data-macro-note.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test that data macros have a note above them pointing to their data sources.
3 | *
4 | * Example 1: If you are using e.g. the {{compat}} or {{cssinfo}} macros, there should
5 | * be a hidden note for contributors in edit-mode, so that they know where the data comes
6 | * from and how to change it.
7 | *
8 | * Implementation notes: This test checks if there is a note for each macro
9 | * (by checking whether the sibling element before the element containing the macro
10 | * has class="hidden") and whether that note contains a link (i.e. an
11 | * element; it doesn't check whether the link is valid).
12 | */
13 |
14 | const ERROR = require('./doctests.js').ERROR;
15 |
16 | const dataMacros = /^(?:compat|css_ref|cssanimatedproperties|cssinfo|csssyntax|webextbrowsercompat)$/i;
17 |
18 | exports.dataMacroNote = {
19 | name: "data_macro_note",
20 | desc: "data_macro_note_desc",
21 | check: function checkDataMacroNote(rootElement) {
22 | let treeWalker = document.createTreeWalker(
23 | rootElement,
24 | NodeFilter.SHOW_TEXT,
25 | {
26 | // eslint-disable-next-line
27 | acceptNode: node => node.textContent.match(/\{\{.*?\}\}/) ?
28 | NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
29 | }
30 | );
31 | let matches = [];
32 |
33 | while (treeWalker.nextNode()) {
34 | let reMacroName = /\{\{\s*([^(}\s]+).*?\}\}/g;
35 | let macroNameMatch = reMacroName.exec(treeWalker.currentNode.textContent);
36 | while (macroNameMatch) {
37 | let noteElement = treeWalker.currentNode.parentNode.previousSibling;
38 | if (dataMacros.test(macroNameMatch[1])) {
39 | if (!noteElement || !noteElement.classList.contains("hidden")) {
40 | matches.push({
41 | msg: "data_macro_note_missing",
42 | msgParams: [macroNameMatch[0]],
43 | type: ERROR
44 | });
45 | } else if (!noteElement.querySelector("[href^='http']")) {
46 | matches.push({
47 | msg: "data_macro_source_link_missing",
48 | msgParams: [macroNameMatch[0]],
49 | type: ERROR
50 | });
51 | }
52 | }
53 | macroNameMatch = reMacroName.exec(treeWalker.currentNode.textContent);
54 | }
55 | }
56 |
57 | return matches;
58 | }
59 | };
60 |
--------------------------------------------------------------------------------
/lib/unnecessary-macro-params.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for obsolete macro parameters.
3 | *
4 | * Example 1: Some macros like {{JSRef}} don't require parameters anymore, so they should be
5 | * removed.
6 | *
7 | * Example 2: Some macros like {{cssinfo}} don't require parameters when the related information
8 | * can be read from the page's slug, so they should be removed in those cases.
9 | *
10 | * Implementation notes: This test checks for a specific list of macros, which either have no
11 | * parameters at all or their parameters are redundant. It uses the page title for comparison, so
12 | * the unit test doesn't break while working on about:blank.
13 | */
14 |
15 |
16 | const ERROR = require('./doctests.js').ERROR;
17 |
18 | const reMacrosNotRequiringParams = /\{\{\s*(?:JSRef|csssyntax|cssinfo|svginfo)\([^)]+?\)\s*\}\}/i;
19 | const reMacrosNotRequiringParamsGlobal = new RegExp(reMacrosNotRequiringParams.source, "gi");
20 |
21 | exports.unnecessaryMacroParams = {
22 | name: "unnecessary_macro_params",
23 | desc: "unnecessary_macro_params_desc",
24 | check: function checkUnnecessaryMacroParams(rootElement) {
25 | let treeWalker = document.createTreeWalker(
26 | rootElement,
27 | NodeFilter.SHOW_TEXT,
28 | {
29 | // eslint-disable-next-line
30 | acceptNode: node => node.textContent.match(reMacrosNotRequiringParams) ?
31 | NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT
32 | }
33 | );
34 | let matches = [];
35 |
36 | while (treeWalker.nextNode()) {
37 | let textNodeMatches = treeWalker.currentNode.textContent.match(
38 | reMacrosNotRequiringParamsGlobal) || [];
39 | textNodeMatches.forEach(match => {
40 | let paramMatch = match.match(/(?:csssyntax|cssinfo|svginfo)\((["'])(.+?)\1/i);
41 | if (paramMatch) {
42 | let param = paramMatch[2];
43 | if (param === document.title.replace(/^(.+?) \| Edit.*$/, "$1")) {
44 | matches.push({
45 | msg: "macro_with_unnecessary_params_equalling_slug",
46 | msgParams: [match],
47 | type: ERROR
48 | });
49 | }
50 | } else {
51 | matches.push({
52 | msg: "macro_with_unused_params",
53 | msgParams: [match],
54 | type: ERROR
55 | });
56 | }
57 | });
58 | }
59 |
60 | return matches;
61 | }
62 | };
63 |
--------------------------------------------------------------------------------
/lib/empty-elements.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for empty elements.
3 | *
4 | * Example 1: Paragraphs only containing a non-breaking space (
) should be avoided.
5 | *
6 | * Implementation notes: This test checks for elements containing no text or only space
7 | * characters excluding the new paragraph helper of CKEditor and self-closing elements except
8 | *
and elements.
9 | */
10 |
11 | const ERROR = require('./doctests.js').ERROR;
12 | const WARNING = require('./doctests.js').WARNING;
13 |
14 | const isNewParagraphHelper = require('./doctests.js').isNewParagraphHelper;
15 |
16 | exports.emptyElements = {
17 | name: "empty_elements",
18 | desc: "empty_elements_desc",
19 | check: function checkEmptyElements(rootElement) {
20 | let treeWalker = document.createTreeWalker(
21 | rootElement,
22 | NodeFilter.SHOW_ELEMENT,
23 | {
24 | acceptNode: node => {
25 | // matching self-closing elements and td elements and excluding them
26 | if (!node.localName.match(/^link|track|param|area|command|col|base|meta|hr|source|img|keygen|br|wbr|input$/i) &&
27 | !node.localName.match(/^td$/i) && node.textContent.match(/^(?: |\s|\n)*$/)) {
28 | // Exclude new paragraph helper
29 | if (isNewParagraphHelper(node.firstElementChild)) {
30 | return NodeFilter.FILTER_REJECT;
31 | }
32 |
33 | // Elements containing self-closing elements except
and are considered non-empty
34 | let descendantSelfClosingElements = node.querySelectorAll(
35 | "link,track,param,area,command,col,base,meta,hr,source,img,keygen,input");
36 | return descendantSelfClosingElements.length === 0 ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
37 | }
38 | return NodeFilter.FILTER_SKIP;
39 | }
40 | }
41 | );
42 | let matches = [];
43 |
44 | while (treeWalker.nextNode()) {
45 | matches.push({
46 | node: treeWalker.currentNode,
47 | msg: treeWalker.currentNode.outerHTML,
48 | type: treeWalker.currentNode.localName === "td" ? WARNING : ERROR
49 | });
50 | }
51 |
52 | return matches;
53 | },
54 |
55 | fix: function fixEmptyElements(matches) {
56 | matches.forEach(match => {
57 | if (match.type === ERROR) {
58 | match.node.remove();
59 | }
60 | });
61 | }
62 | };
63 |
--------------------------------------------------------------------------------
/lib/wrong-syntax-class.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for whether the 'syntax' class is properly used on a syntax block.
3 | *
4 | * Example 1: elements following a 'Formal syntax' heading are expected to contain a syntax
5 | * definition, which needs to be styled using class="syntaxbox".
6 | *
7 | * Example 2: elements following a 'Syntax' heading where there is no 'Formal syntax'
8 | * section are expected to contain a syntax definition, which needs to be styled using
9 | * class="syntaxbox".
10 | *
11 | * Implementation notes: This test first searches for an Formal syntax
heading. If none
12 | * is found, it searches for a Syntax
heading. If one of those is found, the following
13 | * element is expected to hold a syntax definition, which needs to be styled using
14 | * class="syntaxbox".
15 | */
16 |
17 | const ERROR = require('./doctests.js').ERROR;
18 |
19 | exports.wrongSyntaxClass = {
20 | name: "wrong_syntax_class",
21 | desc: "wrong_syntax_class_desc",
22 | check: function checkWrongSyntaxClass(rootElement) {
23 | function checkPre(heading) {
24 | let element = heading.nextSibling;
25 | while (element && element.localName !== "h2") {
26 | if (element.localName === "pre" && element.className !== "syntaxbox") {
27 | return {
28 | node: element,
29 | msg: "wrong_syntax_class_used",
30 | msgParams: [element.className],
31 | type: ERROR
32 | };
33 | }
34 | element = element.nextElementSibling;
35 | }
36 | return undefined;
37 | }
38 |
39 | let subHeadings = rootElement.getElementsByTagName("h3");
40 | let formalSyntaxSection = null;
41 | for (let i = 0; !formalSyntaxSection && i < subHeadings.length; i++) {
42 | if (subHeadings[i].textContent.match(/Formal syntax/i)) {
43 | formalSyntaxSection = subHeadings[i];
44 | }
45 | }
46 |
47 | let matches = [];
48 | if (formalSyntaxSection) {
49 | let match = checkPre(formalSyntaxSection);
50 | if (match) {
51 | matches.push(match);
52 | }
53 | } else {
54 | let headings = rootElement.getElementsByTagName("h2");
55 | let syntaxSection = null;
56 | for (let i = 0; !syntaxSection && i < headings.length; i++) {
57 | if (headings[i].textContent.toLowerCase() === "syntax") {
58 | syntaxSection = headings[i];
59 | }
60 | }
61 |
62 | if (syntaxSection) {
63 | let match = checkPre(syntaxSection);
64 | if (match) {
65 | matches.push(match);
66 | }
67 | }
68 | }
69 |
70 | return matches;
71 | },
72 | fix: function fixWrongSyntaxClass(matches) {
73 | matches.forEach(match => {
74 | match.node.className = "syntaxbox";
75 | });
76 | }
77 | };
78 |
--------------------------------------------------------------------------------
/tests/test-wrong-highlighted-line.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const wrongHighlightedLine = require('../lib/wrong-highlighted-line.js').wrongHighlightedLine;
4 |
5 | describe('wrongHighlightedLine', function() {
6 | it('Should return 10 errors related to wrong highlighted line', function(done) {
7 | const str = 'foo\nbar
' +
8 | 'foo\nbar
' +
9 | 'foo\nbar
' +
10 | 'foo\nbar\nbaz
' +
11 | 'foo\nbar\nbaz
' +
12 | 'foo\nbar\nbaz\nbax\nbix
' +
13 | 'foo\nbar\nbaz\nbax\nbix
' +
14 | 'foo\nbar
' +
15 | 'foo\nbar
' +
16 | 'foo\nbar
' +
17 | 'foo
bar
' +
18 | 'foo
bar
' +
19 | 'foo
bar
' +
20 | 'foo\nbar\nbaz
' +
21 | 'foo\nbar\nbaz
';
22 |
23 | const expected = [
24 | {msg: "highlighted_line_number_not_positive", msgParams: ["0", "0"], type: ERROR},
25 | {msg: "highlighted_line_number_not_positive", msgParams: ["-1", "-1"], type: ERROR},
26 | {msg: "highlighted_line_number_too_big", msgParams: ["3", "2", "3"], type: ERROR},
27 | {msg: "highlighted_line_number_too_big", msgParams: ["3", "2", "3"], type: ERROR},
28 | {msg: "highlighted_line_number_too_big", msgParams: ["3", "2", "3"], type: ERROR},
29 | {msg: "highlighted_line_number_too_big", msgParams: ["3", "2", "3"], type: ERROR},
30 | {msg: "highlighted_line_number_not_positive", msgParams: ["-3", "1,-3--5,3"], type: ERROR},
31 | {msg: "highlighted_line_number_not_positive", msgParams: ["-5", "1,-3--5,3"], type: ERROR},
32 | {msg: "invalid_highlighted_range", msgParams: ["-3", "-5", "1,-3--5,3"], type: ERROR},
33 | {msg: "highlighted_line_number_too_big", msgParams: ["6", "3", " 1, 3 - 6 ,2 "], type: ERROR}
34 | ];
35 |
36 | let rootElement = document.createElement("body");
37 | rootElement.innerHTML = str;
38 |
39 | let results = wrongHighlightedLine.check(rootElement);
40 |
41 | results.forEach((element, index) => {
42 | delete element.node;
43 | assert.deepEqual(expected[index], element);
44 | });
45 |
46 | done();
47 | });
48 | });
49 |
50 |
--------------------------------------------------------------------------------
/lib/api-syntax-headlines.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test whether the API syntax headlines are named correctly, i.e. 'Parameters',
3 | * 'Return value' and 'Exceptions', and whether they appear in the correct order.
4 | *
5 | * Example 1: The return value should have 'Return value' as headline and not 'Returns'.
6 | *
7 | * Example 2: Exceptions should have 'Exceptions' as headline and not 'Errors' or 'Errors thrown'.
8 | *
9 | * Example 3: Having an 'Exceptions' section before the 'Return value' section should be avoided.
10 | * The correct order of the sections is 'Parameters', 'Return value' and 'Exceptions'.
11 | *
12 | * Implementation notes: This test searches for specific keywords like 'returns' or 'errors' and
13 | * expects the headlines to be elements under a Syntax
section.
14 | */
15 |
16 | const ERROR = require('./doctests.js').ERROR;
17 |
18 | const disallowedNames = new Map([
19 | ["arguments", "Parameters"],
20 | ["returns", "Return value"],
21 | ["errors", "Exceptions"],
22 | ["errors thrown", "Exceptions"],
23 | ["exceptions thrown", "Exceptions"]
24 | ]);
25 | const validOrder = [
26 | new Set(["parameters", "arguments"]),
27 | new Set(["return value", "returns"]),
28 | new Set(["exceptions", "exceptions thrown", "errors", "errors thrown"])
29 | ];
30 |
31 | exports.apiSyntaxHeadlines = {
32 | name: "api_syntax_headlines",
33 | desc: "api_syntax_headlines_desc",
34 |
35 | check: function checkAPISyntaxHeadlines(rootElement) {
36 | let headlines = rootElement.getElementsByTagName("h2");
37 | let syntaxSection = null;
38 | let order = [];
39 | let matches = [];
40 | for (let i = 0; !syntaxSection && i < headlines.length; i++) {
41 | if (headlines[i].textContent === "Syntax") {
42 | syntaxSection = headlines[i];
43 | }
44 | }
45 |
46 | if (syntaxSection) {
47 | let subHeadingElements = [];
48 | let element = syntaxSection.nextSibling;
49 | while (element && element.localName !== "h2") {
50 | if (element.localName === "h3") {
51 | subHeadingElements.push(element);
52 | }
53 | element = element.nextSibling;
54 | }
55 | for (let i = 0; i < subHeadingElements.length; i++) {
56 | let subHeading = subHeadingElements[i].textContent.toLowerCase();
57 | for (let j = 0; j < validOrder.length; j++) {
58 | let heading = validOrder[j];
59 | if (heading.has(subHeading)) {
60 | order.push(j);
61 | }
62 | }
63 | if (disallowedNames.has(subHeading)) {
64 | matches.push({
65 | node: subHeadingElements[i],
66 | msg: "invalid_headline_name",
67 | msgParams: [subHeadingElements[i].textContent],
68 | type: ERROR
69 | });
70 | }
71 | }
72 |
73 | // Check the order of the headlines
74 | for (let i = 1; i < order.length; i++) {
75 | if (order[i] < order[i - 1]) {
76 | matches.push({
77 | msg: "invalid_headline_order",
78 | type: ERROR
79 | });
80 | }
81 | }
82 | }
83 |
84 | return matches;
85 | },
86 |
87 | fix: function fixAPISyntaxHeadlines(matches) {
88 | matches.forEach(match => {
89 | switch (match.msg) {
90 | case "invalid_headline_name":
91 | match.node.textContent = disallowedNames.get(match.node.textContent.toLowerCase());
92 | break;
93 | }
94 | });
95 | }
96 | };
97 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | // Karma configuration
2 | // Generated on Mon Jun 19 2017 18:08:59 GMT+0200 (CEST)
3 |
4 | module.exports = function(config) {
5 | config.set({
6 |
7 | // base path that will be used to resolve all patterns (eg. files, exclude)
8 | basePath: '',
9 |
10 |
11 | // frameworks to use
12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
13 | frameworks: ['mocha', 'browserify'],
14 |
15 |
16 | // list of files / patterns to load in the browser
17 | files: [
18 | 'lib/doctests.js',
19 | 'lib/*.js',
20 | 'tests/test-absolute-urls-for-internal-links.js',
21 | 'tests/test-alert-print-in-code.js',
22 | 'tests/test-api-syntax-headlines.js',
23 | 'tests/test-article-length.js',
24 | 'tests/test-code-in-pre.js',
25 | 'tests/test-data-macro-note.js',
26 | 'tests/test-different-locale-links.js',
27 | 'tests/test-empty-brackets.js',
28 | 'tests/test-empty-elements.js',
29 | 'tests/test-example-colon-heading.js',
30 | 'tests/test-font-elements.js',
31 | 'tests/test-html-comments.js',
32 | 'tests/test-http-links.js',
33 | 'tests/test-incorrectly-wrapped-sidebar-macros.js',
34 | 'tests/test-invalid-macros.js',
35 | 'tests/test-line-length-in-pre.js',
36 | 'tests/test-macro-syntax-error.js',
37 | 'tests/test-name-attribute.js',
38 | 'tests/test-old-urls.js',
39 | 'tests/test-pre-without-class.js',
40 | 'tests/test-shell-prompts.js',
41 | 'tests/test-span-count.js',
42 | 'tests/test-style-attribute.js',
43 | 'tests/test-summary-heading.js',
44 | 'tests/test-unnecessary-macro-params.js',
45 | 'tests/test-url-in-link-title.js',
46 | 'tests/test-wrong-highlighted-line.js',
47 | 'tests/test-wrong-syntax-class.js',
48 | 'tests/test-link-count.js',
49 | 'tests/test-anchor-exists.js',
50 | 'tests/test-mixed-content.js'
51 | ],
52 |
53 |
54 | // list of files to exclude
55 | exclude: [
56 | ],
57 |
58 |
59 | // preprocess matching files before serving them to the browser
60 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
61 | preprocessors: {
62 | 'lib/*.js': ['browserify'],
63 | 'tests/*.js' : ['browserify'],
64 | },
65 |
66 |
67 | // test results reporter to use
68 | // possible values: 'dots', 'progress'
69 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter
70 | reporters: ['mocha'],
71 |
72 |
73 | // web server port
74 | port: 9876,
75 |
76 |
77 | // enable / disable colors in the output (reporters and logs)
78 | colors: true,
79 |
80 |
81 | // level of logging
82 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
83 | logLevel: config.LOG_INFO,
84 |
85 |
86 | // enable / disable watching file and executing tests whenever any file changes
87 | autoWatch: true,
88 |
89 |
90 | // start these browsers
91 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
92 | browsers: ['Chrome', 'Firefox', 'Nightmare'],
93 |
94 |
95 | // Continuous Integration mode
96 | // if true, Karma captures browsers, runs the tests and exits
97 | singleRun: false,
98 |
99 | // Concurrency level
100 | // how many browser should be started simultaneous
101 | concurrency: Infinity
102 | })
103 | }
104 |
--------------------------------------------------------------------------------
/lib/macro-syntax-error.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for syntax errors in macro calls.
3 | *
4 | * Example 1: {{macro} misses a closing curly brace, so it will be recognized as error.
5 | *
6 | * Example 2: {{macro('param'}} misses a closing bracket, so it will be recognized as error.
7 | *
8 | * Example 3: {{macro("param"))}} has an additional closing bracket, so it will be recognized as error.
9 | *
10 | * Example 4: {{macro('param)}} and {{macro(param")}} have incorrectly quoted string parameters,
11 | * so they will be recognized as errors.
12 | *
13 | * Implementation notes: This test uses regular expressions to recognize invalid macros.
14 | * It currently fails to properly validate macros containing JSON parameters (see issue #139).
15 | */
16 |
17 | const ERROR = require('./doctests.js').ERROR;
18 |
19 | exports.macroSyntaxError = {
20 | name: "macro_syntax_error",
21 | desc: "macro_syntax_error_desc",
22 | check: function checkMacroSyntaxError(rootElement) {
23 | function validateStringParams(macro) {
24 | let paramListStartIndex = macro.indexOf("(") + 1;
25 | let paramListEndMatch = macro.match(/\)*\s*\}{1,2}$/);
26 | let paramListEndIndex = macro.length - paramListEndMatch[0].length;
27 | let stringParamQuote = "";
28 | for (let i = paramListStartIndex; i < paramListEndIndex; i++) {
29 | if (macro[i] === "\"") {
30 | if (stringParamQuote === "") {
31 | stringParamQuote = "\"";
32 | } else if (stringParamQuote === "\"" && macro[i - 1] !== "\\") {
33 | stringParamQuote = "";
34 | }
35 | } else if (macro[i] === "'") {
36 | if (stringParamQuote === "") {
37 | stringParamQuote = "'";
38 | } else if (stringParamQuote === "'" && macro[i - 1] !== "\\") {
39 | stringParamQuote = "";
40 | }
41 | } else if (stringParamQuote === "" && macro[i].match(/[^\s,\d\-.]/)) {
42 | return false;
43 | }
44 | }
45 | return stringParamQuote === "";
46 | }
47 |
48 | let treeWalker = document.createTreeWalker(
49 | rootElement,
50 | NodeFilter.SHOW_TEXT,
51 | // eslint-disable-next-line
52 | {acceptNode: node => node.textContent.match(/\{\{[^\(\}]*\([^\}]*\}\}|\{\{[^\}]*?\}(?:(?=[^\}])|$)/) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT}
53 | );
54 | let matches = [];
55 |
56 | while (treeWalker.nextNode()) {
57 | let textNodeMatches = treeWalker.currentNode.textContent.match(/\{\{[^(}]*\([^}]*\}\}|\{\{[^}]*?\}(?:(?=[^}])|$)/gi) || [];
58 | textNodeMatches.forEach(macro => {
59 | if (macro.match(/[^}]\}$/)) {
60 | matches.push({
61 | msg: "missing_closing_curly_brace",
62 | msgParams: [macro],
63 | type: ERROR
64 | });
65 | }
66 | if (macro.match(/^\{\{[^(]+\(.+?[^)\s]\s*\}\}$/)) {
67 | matches.push({
68 | msg: "missing_closing_bracket",
69 | msgParams: [macro],
70 | type: ERROR
71 | });
72 | }
73 | if (!validateStringParams(macro)) {
74 | matches.push({
75 | msg: "string_parameter_incorrectly_quoted",
76 | msgParams: [macro],
77 | type: ERROR
78 | });
79 | }
80 | if (macro.match(/\){2,}\}{1,2}$/)) {
81 | matches.push({
82 | msg: "additional_closing_bracket",
83 | msgParams: [macro],
84 | type: ERROR
85 | });
86 | }
87 | });
88 | }
89 |
90 | return matches;
91 | }
92 | };
93 |
--------------------------------------------------------------------------------
/tests/test-macro-syntax-error.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const macroSyntaxError = require('../lib/macro-syntax-error.js').macroSyntaxError;
4 |
5 | describe('macroSyntaxError', function() {
6 | it('Should return 19 errors regarding macro syntax', function(done) {
7 | const str = '{{macro}}' +
8 | '{{ macro }}' +
9 | '{{macro("param")}}' +
10 | '{{ macro("param") }}' +
11 | '{{macro(123)}}' +
12 | '{{macro(123, "param")}}' +
13 | '{{macro(\'param\', 123, "param")}}' +
14 | '{{macro("param)}}' + // Missing closing double quote
15 | '{{macro(\'param)}}' + // Missing closing single quote
16 | '{{macro(param)}}' + // Missing quotes
17 | '{{macro(param")}}' + // Missing opening double quote
18 | '{{macro(param\')}}' + // Missing opening single quote
19 | '{{macro(\'param\', 123, "param)}}' + // Missing closing double quote, multiple parameters
20 | '{{macro("param"))}}' + // Double closing parameter list bracket
21 | '{{macro("param")}' + // Missing closing macro curly brace after double quoted parameter
22 | '{{macro(\'param\')}' + // Missing closing macro curly brace after single quoted parameter
23 | '{{macro("param"}}' + // Missing closing parameter list bracket after double quoted parameter
24 | '{{macro(\'param\'}}' + // Missing closing parameter list bracket after single quoted parameter
25 | '{{macro(param"}}' + // Missing opening double quote and missing closing parameter list bracket
26 | '{{macro(param"))}}' + // Missing opening double quote and double closing parameter list bracket
27 | '{{macro(123, "param()"}}'; // Missing closing parameter list bracket after string parameter containing bracket
28 |
29 | const expected = [
30 | {msg: "string_parameter_incorrectly_quoted", msgParams: ['{{macro("param)}}'], type: ERROR},
31 | {msg: "string_parameter_incorrectly_quoted", msgParams: ["{{macro('param)}}"], type: ERROR},
32 | {msg: "string_parameter_incorrectly_quoted", msgParams: ["{{macro(param)}}"], type: ERROR},
33 | {msg: "string_parameter_incorrectly_quoted", msgParams: ['{{macro(param")}}'], type: ERROR},
34 | {msg: "string_parameter_incorrectly_quoted", msgParams: ["{{macro(param')}}"], type: ERROR},
35 | {msg: "string_parameter_incorrectly_quoted", msgParams: ["{{macro('param', 123, \"param)}}"], type: ERROR},
36 | {msg: "additional_closing_bracket", msgParams: ['{{macro("param"))}}'], type: ERROR},
37 | {msg: "missing_closing_curly_brace", msgParams: ['{{macro("param")}'], type: ERROR},
38 | {msg: "missing_closing_curly_brace", msgParams: ["{{macro(\'param\')}"], type: ERROR},
39 | {msg: "missing_closing_bracket", msgParams: ['{{macro("param"}}'], type: ERROR},
40 | {msg: "missing_closing_bracket", msgParams: ["{{macro(\'param\'}}"], type: ERROR},
41 | {msg: "missing_closing_bracket", msgParams: ['{{macro(param"}}'], type: ERROR},
42 | {msg: "string_parameter_incorrectly_quoted", msgParams: ['{{macro(param"}}'], type: ERROR},
43 | {msg: "string_parameter_incorrectly_quoted", msgParams: ['{{macro(param"))}}'], type: ERROR},
44 | {msg: "additional_closing_bracket", msgParams: ['{{macro(param"))}}'], type: ERROR},
45 | {msg: "missing_closing_bracket", msgParams: ['{{macro(123, "param()"}}'], type: ERROR}
46 | ];
47 |
48 | let rootElement = document.createElement("body");
49 | rootElement.innerHTML = str;
50 |
51 | let results = macroSyntaxError.check(rootElement);
52 |
53 | results.forEach((element, index) => {
54 | delete element.node;
55 | assert.deepEqual(expected[index], element);
56 | });
57 |
58 | done();
59 | });
60 | });
61 |
62 |
--------------------------------------------------------------------------------
/lib/wrong-highlighted-line.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for incorrect line highlights in code examples.
3 | *
4 | * Example 1: Negative highlights like in
5 | * var x = 1;
6 | * are invalid.
7 | *
8 | * Example 2: Highlights exceeding the line count like in
9 | * var x = 1;
10 | * are invalid.
11 | *
12 | * Example 3: Highlighted ranges of lines exceeding the line count like in
13 | * var x = 1;
14 | * are invalid.
15 | *
16 | * Example 4: Highlighted ranges where the start line is bigger than the end line line in
17 | * var x = 1;\nvar y = 2;
18 | * are invalid.
19 | *
20 | * Implementation notes: This test searches for all elements containing a 'highlight'
21 | * class, then splits the numbers and ranges wrapped by square brackets following the 'highlight'
22 | * class and finally checks each item whether its valid.
23 | */
24 |
25 | const ERROR = require('./doctests.js').ERROR;
26 |
27 | const reHighlighting = /highlight:?\s*\[(.+?)\]/i;
28 |
29 | exports.wrongHighlightedLine = {
30 | name: "wrong_highlighted_line",
31 | desc: "wrong_highlighted_line_desc",
32 |
33 | check: function checkWrongHighlightedLine(rootElement) {
34 | let presWithHighlighting = rootElement.querySelectorAll("pre[class*='highlight']");
35 | let matches = [];
36 |
37 | for (let i = 0; i < presWithHighlighting.length; i++) {
38 | let match = presWithHighlighting[i].getAttribute("class").match(reHighlighting);
39 | if (match) {
40 | let numbersAndRanges = match[1].split(",");
41 | let lineCount = presWithHighlighting[i].innerHTML.split(/
|\n/gi).length;
42 |
43 | numbersAndRanges.forEach(numberOrRange => {
44 | let start;
45 | let end;
46 | [, start, end] = numberOrRange.match(/^\s*(-?\d+)(?:\s*-\s*(-?\d+))?\s*$/);
47 |
48 | if (start === undefined) {
49 | return;
50 | }
51 |
52 | start = Number(start);
53 | end = Number(end);
54 |
55 | if (start <= 0) {
56 | matches.push({
57 | node: presWithHighlighting[i],
58 | msg: "highlighted_line_number_not_positive",
59 | msgParams: [String(start), match[1]],
60 | type: ERROR
61 | });
62 | }
63 | if (start > lineCount) {
64 | matches.push({
65 | node: presWithHighlighting[i],
66 | msg: "highlighted_line_number_too_big",
67 | msgParams: [String(start), String(lineCount), match[1]],
68 | type: ERROR
69 | });
70 | }
71 | if (!Number.isNaN(end)) {
72 | if (end > lineCount) {
73 | matches.push({
74 | node: presWithHighlighting[i],
75 | msg: "highlighted_line_number_too_big",
76 | msgParams: [String(end), String(lineCount), match[1]],
77 | type: ERROR
78 | });
79 | }
80 | if (end <= 0) {
81 | matches.push({
82 | node: presWithHighlighting[i],
83 | msg: "highlighted_line_number_not_positive",
84 | msgParams: [String(end), match[1]],
85 | type: ERROR
86 | });
87 | }
88 | if (start > end) {
89 | matches.push({
90 | node: presWithHighlighting[i],
91 | msg: "invalid_highlighted_range",
92 | msgParams: [String(start), String(end), match[1]],
93 | type: ERROR
94 | });
95 | }
96 | }
97 | });
98 | }
99 | }
100 |
101 | return matches;
102 | },
103 |
104 | fix: function fixWrongHighlightedLine(matches) {
105 | matches.forEach(match => {
106 | match.node.className = match.node.className.replace(reHighlighting, "").replace(/;\s*$/, "");
107 | });
108 | }
109 | };
110 |
--------------------------------------------------------------------------------
/lib/invalid-macros.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Title: Test for the usage of invalid macros.
3 | *
4 | * Example 1: The usage of {{SomeMacro}}
should rather be removed, replaced by a valid
5 | * macro or by static text and {{SomeMacro}} should be deleted.
6 | *
7 | * Implementation notes: This test uses an (incomprehensive) whitelist of allowed macros and a
8 | * list of obsolete macros. Obsolete macros are marked as errors, all others, which are not
9 | * whitelisted are marked as warnings.
10 | */
11 |
12 | const ERROR = require('./doctests.js').ERROR;
13 | const WARNING = require('./doctests.js').WARNING;
14 |
15 | const obsoleteMacros = [
16 | "languages"
17 | ];
18 |
19 | exports.invalidMacros = {
20 | name: "invalid_macros",
21 | desc: "invalid_macros_desc",
22 | check: function checkInvalidMacros(rootElement) {
23 | const allowedMacros = [
24 | "addonsidebar",
25 | "apiref",
26 | "anch",
27 | "availableinworkers",
28 | "bug",
29 | "canvassidebar",
30 | "chromebug",
31 | "communitybox",
32 | "compat",
33 | "cssdata",
34 | "cssinfo",
35 | "cssref",
36 | "csssyntax",
37 | "cssxref",
38 | "defaultapisidebar",
39 | "deprecated_header",
40 | "deprecated_inline",
41 | "discussionlist",
42 | "docstatus",
43 | "domxref",
44 | "draft",
45 | "edgebug",
46 | "embedghlivesample",
47 | "embedlivesample",
48 | "embedyoutube",
49 | "event",
50 | "experimental_inline",
51 | "firefox_for_developers",
52 | "fx_minversion_inline",
53 | "fxos_maxversion_inline",
54 | "fxos_minversion_inline",
55 | "gecko",
56 | "gecko_minversion_inline",
57 | "geckorelease",
58 | "glossary",
59 | "groupdata",
60 | "htmlattrdef",
61 | "htmlattrxref",
62 | "htmlelement",
63 | "htmlref",
64 | "httpheader",
65 | "httpmethod",
66 | "httpsidebar",
67 | "httpstatus",
68 | "includesubnav",
69 | "inheritancediagram",
70 | "interface",
71 | "interfacedata",
72 | "jsfiddlelink",
73 | "jsref",
74 | "jssidebar",
75 | "jsxref",
76 | "js_property_attributes",
77 | "l10n:common",
78 | "l10n:compattable",
79 | "l10n:css",
80 | "l10n:javascript",
81 | "l10n:svg",
82 | "localizationstatusinsection",
83 | "mathmlelement",
84 | "mathmlref",
85 | "next",
86 | "non-standard_header",
87 | "non-standard_inline",
88 | "noscript_inline",
89 | "obsolete_header",
90 | "obsolete_inline",
91 | "optional_inline",
92 | "page",
93 | "previous",
94 | "previousmenunext",
95 | "previousnext",
96 | "promote-mdn",
97 | "property_prefix",
98 | "readonlyinline",
99 | "releasegecko",
100 | "rfc",
101 | "seecompattable",
102 | "sidebarutilities",
103 | "sm_minversion_inline",
104 | "spec2",
105 | "specname",
106 | "svgattr",
107 | "svgdata",
108 | "svgelement",
109 | "svginfo",
110 | "svgref",
111 | "tb_minversion_inline",
112 | "webextapiembedtype",
113 | "webextapiref",
114 | "webextapisidebar",
115 | "webextchromecompat",
116 | "webextexamplesdata",
117 | "webextexamples",
118 | "webglsidebar",
119 | "webkitbug",
120 | "webrtcsidebar",
121 | "xref",
122 | "xulattr",
123 | "xulelem"
124 | ];
125 |
126 | let treeWalker = document.createTreeWalker(
127 | rootElement,
128 | NodeFilter.SHOW_TEXT,
129 | // eslint-disable-next-line
130 | {acceptNode: node => node.textContent.match(/\{\{.*?\}\}/) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT}
131 | );
132 | let matches = [];
133 |
134 | while (treeWalker.nextNode()) {
135 | let reMacroName = /\{\{\s*([^(}\s]+).*?\}\}/g;
136 | let macroNameMatch = reMacroName.exec(treeWalker.currentNode.textContent);
137 | while (macroNameMatch) {
138 | if (obsoleteMacros.includes(macroNameMatch[1].toLowerCase())) {
139 | matches.push({
140 | node: treeWalker.currentNode,
141 | msg: "obsolete_macro",
142 | msgParams: [macroNameMatch[0]],
143 | type: ERROR
144 | });
145 | } else if (!allowedMacros.includes(macroNameMatch[1].toLowerCase())) {
146 | matches.push({
147 | msg: macroNameMatch[0],
148 | type: WARNING
149 | });
150 | }
151 | macroNameMatch = reMacroName.exec(treeWalker.currentNode.textContent);
152 | }
153 | }
154 |
155 | return matches;
156 | },
157 |
158 | fix: function fixInvalidMacros(matches) {
159 | let reObsoleteMacros =
160 | new RegExp(`\\{\\{\\s*(?:${obsoleteMacros.join("|")}).*?\\}\\}`, "gi");
161 |
162 | matches.forEach(match => {
163 | if (!match.node) {
164 | return;
165 | }
166 |
167 | match.node.textContent = match.node.textContent.replace(reObsoleteMacros, "");
168 | if (match.node.parentNode.textContent.match(/^(\s| )*$/)) {
169 | match.node.parentNode.remove();
170 | }
171 | });
172 | }
173 | };
174 |
--------------------------------------------------------------------------------
/tests/test-incorrectly-wrapped-sidebar-macros.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const ERROR = require('../lib/doctests.js').ERROR;
3 | const incorrectlyWrappedSidebarMacros = require('../lib/incorrectly-wrapped-sidebar-macros.js').incorrectlyWrappedSidebarMacros;
4 |
5 | describe('incorrectlyWrappedSidebarMacros', function() {
6 | it('Should return 0 errors (CSSRef)', function(done) {
7 | const str = '{{CSSRef}}';
8 | const expected = [];
9 |
10 | let rootElement = document.createElement("body");
11 | rootElement.innerHTML = str;
12 |
13 | let results = incorrectlyWrappedSidebarMacros.check(rootElement);
14 |
15 | results.forEach((element, index) => {
16 | delete element.node;
17 | assert.deepEqual(expected[index], element);
18 | });
19 |
20 | done();
21 | });
22 |
23 | it('Should return 0 errors (HTMLRef)', function(done) {
24 |
25 | const str = '{{HTMLRef}}';
26 | const expected = [];
27 |
28 | let rootElement = document.createElement("body");
29 | rootElement.innerHTML = str;
30 |
31 | let results = incorrectlyWrappedSidebarMacros.check(rootElement);
32 |
33 | results.forEach((element, index) => {
34 | delete element.node;
35 | assert.deepEqual(expected[index], element);
36 | });
37 |
38 | done();
39 | });
40 |
41 | it('Should return 0 errors (APIRef)', function(done) {
42 |
43 | const str = '{{APIRef}}';
44 | const expected = [];
45 |
46 | let rootElement = document.createElement("body");
47 | rootElement.innerHTML = str;
48 |
49 | let results = incorrectlyWrappedSidebarMacros.check(rootElement);
50 |
51 | results.forEach((element, index) => {
52 | delete element.node;
53 | assert.deepEqual(expected[index], element);
54 | });
55 |
56 | done();
57 | });
58 |
59 | it('Should return 0 errors (JSRef)', function(done) {
60 |
61 | const str = '{{JSRef}}';
62 | const expected = [];
63 |
64 | let rootElement = document.createElement("body");
65 | rootElement.innerHTML = str;
66 |
67 | let results = incorrectlyWrappedSidebarMacros.check(rootElement);
68 |
69 | results.forEach((element, index) => {
70 | delete element.node;
71 | assert.deepEqual(expected[index], element);
72 | });
73 |
74 | done();
75 | });
76 |
77 | it('Should return 0 errors (SVGRefElem)', function(done) {
78 |
79 | const str = '{{SVGRefElem}}';
80 | const expected = [];
81 |
82 | let rootElement = document.createElement("body");
83 | rootElement.innerHTML = str;
84 |
85 | let results = incorrectlyWrappedSidebarMacros.check(rootElement);
86 |
87 | results.forEach((element, index) => {
88 | delete element.node;
89 | assert.deepEqual(expected[index], element);
90 | });
91 |
92 | done();
93 | });
94 |
95 | it('Should return 0 errors (JSSidebar)', function(done) {
96 |
97 | const str = '{{JSSidebar}}';
98 | const expected = [];
99 |
100 | let rootElement = document.createElement("body");
101 | rootElement.innerHTML = str;
102 |
103 | let results = incorrectlyWrappedSidebarMacros.check(rootElement);
104 |
105 | results.forEach((element, index) => {
106 | delete element.node;
107 | assert.deepEqual(expected[index], element);
108 | });
109 |
110 | done();
111 | });
112 |
113 | it('Should return 0 errors (AddonSidebar)', function(done) {
114 |
115 | const str = '{{AddonSidebar}}';
116 | const expected = [];
117 |
118 | let rootElement = document.createElement("body");
119 | rootElement.innerHTML = str;
120 |
121 | let results = incorrectlyWrappedSidebarMacros.check(rootElement);
122 |
123 | results.forEach((element, index) => {
124 | delete element.node;
125 | assert.deepEqual(expected[index], element);
126 | });
127 |
128 | done();
129 | });
130 |
131 | it('Should return 0 errors (APIRef with parameters)', function(done) {
132 |
133 | const str = '{{APIRef("some API")}}';
134 | const expected = [];
135 |
136 | let rootElement = document.createElement("body");
137 | rootElement.innerHTML = str;
138 |
139 | let results = incorrectlyWrappedSidebarMacros.check(rootElement);
140 |
141 | results.forEach((element, index) => {
142 | assert.deepEqual(expected[index], element);
143 | });
144 |
145 | done();
146 | });
147 |
148 | it('Should return 1 error regarding wrapping a sidebar', function(done) {
149 |
150 | const str = '
{{CSSRef}}
';
151 | const expected = [
152 | {msg: "wrong_element_wrapping_sidebar_macro", msgParams: ["{{CSSRef}}", "p"], type: ERROR}
153 | ];
154 |
155 | let rootElement = document.createElement("body");
156 | rootElement.innerHTML = str;
157 |
158 | let results = incorrectlyWrappedSidebarMacros.check(rootElement);
159 |
160 | results.forEach((element, index) => {
161 | delete element.node;
162 | assert.deepEqual(expected[index], element);
163 | });
164 |
165 | done();
166 | });
167 |
168 |
169 | it('Should return 1 error regarding wrapping a sidebar', function(done) {
170 |
171 | const str = '{{ APIRef("Some API") }}';
172 | const expected = [
173 | {msg: "wrong_element_wrapping_sidebar_macro", msgParams: ["{{ APIRef(\"Some API\") }}", "span"], type: ERROR}
174 | ];
175 |
176 | let rootElement = document.createElement("body");
177 | rootElement.innerHTML = str;
178 |
179 | let results = incorrectlyWrappedSidebarMacros.check(rootElement);
180 |
181 | results.forEach((element, index) => {
182 | delete element.node;
183 | assert.deepEqual(expected[index], element);
184 | });
185 |
186 | done();
187 | });
188 | });
189 |
190 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Mozilla Public License Version 2.0
2 | ==================================
3 |
4 | 1. Definitions
5 | --------------
6 |
7 | 1.1. "Contributor"
8 | means each individual or legal entity that creates, contributes to
9 | the creation of, or owns Covered Software.
10 |
11 | 1.2. "Contributor Version"
12 | means the combination of the Contributions of others (if any) used
13 | by a Contributor and that particular Contributor's Contribution.
14 |
15 | 1.3. "Contribution"
16 | means Covered Software of a particular Contributor.
17 |
18 | 1.4. "Covered Software"
19 | means Source Code Form to which the initial Contributor has attached
20 | the notice in Exhibit A, the Executable Form of such Source Code
21 | Form, and Modifications of such Source Code Form, in each case
22 | including portions thereof.
23 |
24 | 1.5. "Incompatible With Secondary Licenses"
25 | means
26 |
27 | (a) that the initial Contributor has attached the notice described
28 | in Exhibit B to the Covered Software; or
29 |
30 | (b) that the Covered Software was made available under the terms of
31 | version 1.1 or earlier of the License, but not also under the
32 | terms of a Secondary License.
33 |
34 | 1.6. "Executable Form"
35 | means any form of the work other than Source Code Form.
36 |
37 | 1.7. "Larger Work"
38 | means a work that combines Covered Software with other material, in
39 | a separate file or files, that is not Covered Software.
40 |
41 | 1.8. "License"
42 | means this document.
43 |
44 | 1.9. "Licensable"
45 | means having the right to grant, to the maximum extent possible,
46 | whether at the time of the initial grant or subsequently, any and
47 | all of the rights conveyed by this License.
48 |
49 | 1.10. "Modifications"
50 | means any of the following:
51 |
52 | (a) any file in Source Code Form that results from an addition to,
53 | deletion from, or modification of the contents of Covered
54 | Software; or
55 |
56 | (b) any new file in Source Code Form that contains any Covered
57 | Software.
58 |
59 | 1.11. "Patent Claims" of a Contributor
60 | means any patent claim(s), including without limitation, method,
61 | process, and apparatus claims, in any patent Licensable by such
62 | Contributor that would be infringed, but for the grant of the
63 | License, by the making, using, selling, offering for sale, having
64 | made, import, or transfer of either its Contributions or its
65 | Contributor Version.
66 |
67 | 1.12. "Secondary License"
68 | means either the GNU General Public License, Version 2.0, the GNU
69 | Lesser General Public License, Version 2.1, the GNU Affero General
70 | Public License, Version 3.0, or any later versions of those
71 | licenses.
72 |
73 | 1.13. "Source Code Form"
74 | means the form of the work preferred for making modifications.
75 |
76 | 1.14. "You" (or "Your")
77 | means an individual or a legal entity exercising rights under this
78 | License. For legal entities, "You" includes any entity that
79 | controls, is controlled by, or is under common control with You. For
80 | purposes of this definition, "control" means (a) the power, direct
81 | or indirect, to cause the direction or management of such entity,
82 | whether by contract or otherwise, or (b) ownership of more than
83 | fifty percent (50%) of the outstanding shares or beneficial
84 | ownership of such entity.
85 |
86 | 2. License Grants and Conditions
87 | --------------------------------
88 |
89 | 2.1. Grants
90 |
91 | Each Contributor hereby grants You a world-wide, royalty-free,
92 | non-exclusive license:
93 |
94 | (a) under intellectual property rights (other than patent or trademark)
95 | Licensable by such Contributor to use, reproduce, make available,
96 | modify, display, perform, distribute, and otherwise exploit its
97 | Contributions, either on an unmodified basis, with Modifications, or
98 | as part of a Larger Work; and
99 |
100 | (b) under Patent Claims of such Contributor to make, use, sell, offer
101 | for sale, have made, import, and otherwise transfer either its
102 | Contributions or its Contributor Version.
103 |
104 | 2.2. Effective Date
105 |
106 | The licenses granted in Section 2.1 with respect to any Contribution
107 | become effective for each Contribution on the date the Contributor first
108 | distributes such Contribution.
109 |
110 | 2.3. Limitations on Grant Scope
111 |
112 | The licenses granted in this Section 2 are the only rights granted under
113 | this License. No additional rights or licenses will be implied from the
114 | distribution or licensing of Covered Software under this License.
115 | Notwithstanding Section 2.1(b) above, no patent license is granted by a
116 | Contributor:
117 |
118 | (a) for any code that a Contributor has removed from Covered Software;
119 | or
120 |
121 | (b) for infringements caused by: (i) Your and any other third party's
122 | modifications of Covered Software, or (ii) the combination of its
123 | Contributions with other software (except as part of its Contributor
124 | Version); or
125 |
126 | (c) under Patent Claims infringed by Covered Software in the absence of
127 | its Contributions.
128 |
129 | This License does not grant any rights in the trademarks, service marks,
130 | or logos of any Contributor (except as may be necessary to comply with
131 | the notice requirements in Section 3.4).
132 |
133 | 2.4. Subsequent Licenses
134 |
135 | No Contributor makes additional grants as a result of Your choice to
136 | distribute the Covered Software under a subsequent version of this
137 | License (see Section 10.2) or under the terms of a Secondary License (if
138 | permitted under the terms of Section 3.3).
139 |
140 | 2.5. Representation
141 |
142 | Each Contributor represents that the Contributor believes its
143 | Contributions are its original creation(s) or it has sufficient rights
144 | to grant the rights to its Contributions conveyed by this License.
145 |
146 | 2.6. Fair Use
147 |
148 | This License is not intended to limit any rights You have under
149 | applicable copyright doctrines of fair use, fair dealing, or other
150 | equivalents.
151 |
152 | 2.7. Conditions
153 |
154 | Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted
155 | in Section 2.1.
156 |
157 | 3. Responsibilities
158 | -------------------
159 |
160 | 3.1. Distribution of Source Form
161 |
162 | All distribution of Covered Software in Source Code Form, including any
163 | Modifications that You create or to which You contribute, must be under
164 | the terms of this License. You must inform recipients that the Source
165 | Code Form of the Covered Software is governed by the terms of this
166 | License, and how they can obtain a copy of this License. You may not
167 | attempt to alter or restrict the recipients' rights in the Source Code
168 | Form.
169 |
170 | 3.2. Distribution of Executable Form
171 |
172 | If You distribute Covered Software in Executable Form then:
173 |
174 | (a) such Covered Software must also be made available in Source Code
175 | Form, as described in Section 3.1, and You must inform recipients of
176 | the Executable Form how they can obtain a copy of such Source Code
177 | Form by reasonable means in a timely manner, at a charge no more
178 | than the cost of distribution to the recipient; and
179 |
180 | (b) You may distribute such Executable Form under the terms of this
181 | License, or sublicense it under different terms, provided that the
182 | license for the Executable Form does not attempt to limit or alter
183 | the recipients' rights in the Source Code Form under this License.
184 |
185 | 3.3. Distribution of a Larger Work
186 |
187 | You may create and distribute a Larger Work under terms of Your choice,
188 | provided that You also comply with the requirements of this License for
189 | the Covered Software. If the Larger Work is a combination of Covered
190 | Software with a work governed by one or more Secondary Licenses, and the
191 | Covered Software is not Incompatible With Secondary Licenses, this
192 | License permits You to additionally distribute such Covered Software
193 | under the terms of such Secondary License(s), so that the recipient of
194 | the Larger Work may, at their option, further distribute the Covered
195 | Software under the terms of either this License or such Secondary
196 | License(s).
197 |
198 | 3.4. Notices
199 |
200 | You may not remove or alter the substance of any license notices
201 | (including copyright notices, patent notices, disclaimers of warranty,
202 | or limitations of liability) contained within the Source Code Form of
203 | the Covered Software, except that You may alter any license notices to
204 | the extent required to remedy known factual inaccuracies.
205 |
206 | 3.5. Application of Additional Terms
207 |
208 | You may choose to offer, and to charge a fee for, warranty, support,
209 | indemnity or liability obligations to one or more recipients of Covered
210 | Software. However, You may do so only on Your own behalf, and not on
211 | behalf of any Contributor. You must make it absolutely clear that any
212 | such warranty, support, indemnity, or liability obligation is offered by
213 | You alone, and You hereby agree to indemnify every Contributor for any
214 | liability incurred by such Contributor as a result of warranty, support,
215 | indemnity or liability terms You offer. You may include additional
216 | disclaimers of warranty and limitations of liability specific to any
217 | jurisdiction.
218 |
219 | 4. Inability to Comply Due to Statute or Regulation
220 | ---------------------------------------------------
221 |
222 | If it is impossible for You to comply with any of the terms of this
223 | License with respect to some or all of the Covered Software due to
224 | statute, judicial order, or regulation then You must: (a) comply with
225 | the terms of this License to the maximum extent possible; and (b)
226 | describe the limitations and the code they affect. Such description must
227 | be placed in a text file included with all distributions of the Covered
228 | Software under this License. Except to the extent prohibited by statute
229 | or regulation, such description must be sufficiently detailed for a
230 | recipient of ordinary skill to be able to understand it.
231 |
232 | 5. Termination
233 | --------------
234 |
235 | 5.1. The rights granted under this License will terminate automatically
236 | if You fail to comply with any of its terms. However, if You become
237 | compliant, then the rights granted under this License from a particular
238 | Contributor are reinstated (a) provisionally, unless and until such
239 | Contributor explicitly and finally terminates Your grants, and (b) on an
240 | ongoing basis, if such Contributor fails to notify You of the
241 | non-compliance by some reasonable means prior to 60 days after You have
242 | come back into compliance. Moreover, Your grants from a particular
243 | Contributor are reinstated on an ongoing basis if such Contributor
244 | notifies You of the non-compliance by some reasonable means, this is the
245 | first time You have received notice of non-compliance with this License
246 | from such Contributor, and You become compliant prior to 30 days after
247 | Your receipt of the notice.
248 |
249 | 5.2. If You initiate litigation against any entity by asserting a patent
250 | infringement claim (excluding declaratory judgment actions,
251 | counter-claims, and cross-claims) alleging that a Contributor Version
252 | directly or indirectly infringes any patent, then the rights granted to
253 | You by any and all Contributors for the Covered Software under Section
254 | 2.1 of this License shall terminate.
255 |
256 | 5.3. In the event of termination under Sections 5.1 or 5.2 above, all
257 | end user license agreements (excluding distributors and resellers) which
258 | have been validly granted by You or Your distributors under this License
259 | prior to termination shall survive termination.
260 |
261 | ************************************************************************
262 | * *
263 | * 6. Disclaimer of Warranty *
264 | * ------------------------- *
265 | * *
266 | * Covered Software is provided under this License on an "as is" *
267 | * basis, without warranty of any kind, either expressed, implied, or *
268 | * statutory, including, without limitation, warranties that the *
269 | * Covered Software is free of defects, merchantable, fit for a *
270 | * particular purpose or non-infringing. The entire risk as to the *
271 | * quality and performance of the Covered Software is with You. *
272 | * Should any Covered Software prove defective in any respect, You *
273 | * (not any Contributor) assume the cost of any necessary servicing, *
274 | * repair, or correction. This disclaimer of warranty constitutes an *
275 | * essential part of this License. No use of any Covered Software is *
276 | * authorized under this License except under this disclaimer. *
277 | * *
278 | ************************************************************************
279 |
280 | ************************************************************************
281 | * *
282 | * 7. Limitation of Liability *
283 | * -------------------------- *
284 | * *
285 | * Under no circumstances and under no legal theory, whether tort *
286 | * (including negligence), contract, or otherwise, shall any *
287 | * Contributor, or anyone who distributes Covered Software as *
288 | * permitted above, be liable to You for any direct, indirect, *
289 | * special, incidental, or consequential damages of any character *
290 | * including, without limitation, damages for lost profits, loss of *
291 | * goodwill, work stoppage, computer failure or malfunction, or any *
292 | * and all other commercial damages or losses, even if such party *
293 | * shall have been informed of the possibility of such damages. This *
294 | * limitation of liability shall not apply to liability for death or *
295 | * personal injury resulting from such party's negligence to the *
296 | * extent applicable law prohibits such limitation. Some *
297 | * jurisdictions do not allow the exclusion or limitation of *
298 | * incidental or consequential damages, so this exclusion and *
299 | * limitation may not apply to You. *
300 | * *
301 | ************************************************************************
302 |
303 | 8. Litigation
304 | -------------
305 |
306 | Any litigation relating to this License may be brought only in the
307 | courts of a jurisdiction where the defendant maintains its principal
308 | place of business and such litigation shall be governed by laws of that
309 | jurisdiction, without reference to its conflict-of-law provisions.
310 | Nothing in this Section shall prevent a party's ability to bring
311 | cross-claims or counter-claims.
312 |
313 | 9. Miscellaneous
314 | ----------------
315 |
316 | This License represents the complete agreement concerning the subject
317 | matter hereof. If any provision of this License is held to be
318 | unenforceable, such provision shall be reformed only to the extent
319 | necessary to make it enforceable. Any law or regulation which provides
320 | that the language of a contract shall be construed against the drafter
321 | shall not be used to construe this License against a Contributor.
322 |
323 | 10. Versions of the License
324 | ---------------------------
325 |
326 | 10.1. New Versions
327 |
328 | Mozilla Foundation is the license steward. Except as provided in Section
329 | 10.3, no one other than the license steward has the right to modify or
330 | publish new versions of this License. Each version will be given a
331 | distinguishing version number.
332 |
333 | 10.2. Effect of New Versions
334 |
335 | You may distribute the Covered Software under the terms of the version
336 | of the License under which You originally received the Covered Software,
337 | or under the terms of any subsequent version published by the license
338 | steward.
339 |
340 | 10.3. Modified Versions
341 |
342 | If you create software not governed by this License, and you want to
343 | create a new license for such software, you may create and use a
344 | modified version of this License if you rename the license and remove
345 | any references to the name of the license steward (except to note that
346 | such modified license differs from this License).
347 |
348 | 10.4. Distributing Source Code Form that is Incompatible With Secondary
349 | Licenses
350 |
351 | If You choose to distribute Source Code Form that is Incompatible With
352 | Secondary Licenses under the terms of this version of the License, the
353 | notice described in Exhibit B of this License must be attached.
354 |
355 | Exhibit A - Source Code Form License Notice
356 | -------------------------------------------
357 |
358 | This Source Code Form is subject to the terms of the Mozilla Public
359 | License, v. 2.0. If a copy of the MPL was not distributed with this
360 | file, You can obtain one at http://mozilla.org/MPL/2.0/.
361 |
362 | If it is not possible or desirable to put the notice in a particular
363 | file, then You may include the notice in a location (such as a LICENSE
364 | file in a relevant directory) where a recipient would be likely to look
365 | for such a notice.
366 |
367 | You may add additional accurate notices of copyright ownership.
368 |
369 | Exhibit B - "Incompatible With Secondary Licenses" Notice
370 | ---------------------------------------------------------
371 |
372 | This Source Code Form is "Incompatible With Secondary Licenses", as
373 | defined by the Mozilla Public License, v. 2.0.
374 |
--------------------------------------------------------------------------------