diff --git a/examples/prism-abap.html b/examples/prism-abap.html index dfda1f7f18..103a454d58 100644 --- a/examples/prism-abap.html +++ b/examples/prism-abap.html @@ -1,11 +1,11 @@
* Line Comments
-" End of line comment used as line comment.
-value = 1. " End of line comment
+" End of line comment used as line comment.
+value = 1. " End of line comment
DATA:
- "! ABAPDoc comment
+ "! ABAPDoc comment
value TYPE i.
@@ -16,7 +16,7 @@ USE: regexp
-R/ abcde?.*+\?\.\*\+\/\\\/idmsr-idmsr/idmsr-idmsr
-
+R/ abcde?.*+\?\.\*\+\/\\\/idmsr-idmsr/idmsr-idmsr
: a ( -- ) ;
@@ -73,9 +72,9 @@ Special words (builtins, conventions)
new last-index + - neg
-<array> <=> SYNTAX: x $[ xyz ]
+<array> <=> SYNTAX: x $[ xyz ]
-set-x change-x with-variable ?of if* (gensym) hex. $description reader>> >>setter writer<<
+set-x change-x with-variable ?of if* (gensym) hex. $description reader>> >>setter writer<<
string>number >hex base> mutater!
diff --git a/examples/prism-liquid.html b/examples/prism-liquid.html
index 7497197558..55058a2651 100644
--- a/examples/prism-liquid.html
+++ b/examples/prism-liquid.html
@@ -3,7 +3,7 @@ Liquid provides multiple control flow statements.
@@ -18,7 +18,7 @@ if
unless
-The opposite of if
– executes a block of code only if a certain condition is not met.
+The opposite of if
– executes a block of code only if a certain condition is not met.
{% unless product.title == 'Awesome Shoes' %}
@@ -28,7 +28,7 @@ unless
case
-Creates a switch statement to compare a variable with different values. case
initializes the switch statement, and when
compares its values.
+Creates a switch statement to compare a variable with different values. case
initializes the switch statement, and when
compares its values.
{% assign handle = 'cake' %}
@@ -44,10 +44,10 @@ case
for
-Repeatedly executes a block of code.
+Repeatedly executes a block of code.
-break = Causes the loop to stop iterating when it encounters the break tag.
-continue = Causes the loop to skip the current iteration when it encounters the continue tag.
+break = Causes the loop to stop iterating when it encounters the break tag.
+continue = Causes the loop to skip the current iteration when it encounters the continue tag.
{% for i in (1..10) %}
diff --git a/examples/prism-markup.html b/examples/prism-markup.html
index ac00e7f637..e99c2f71b4 100644
--- a/examples/prism-markup.html
+++ b/examples/prism-markup.html
@@ -72,6 +72,6 @@ Multi-line attribute values
baz">
XML tags with non-ASCII characters
-<Läufer>foo</Läufer>
-<tag läufer="läufer">bar</tag>
-<läufer:tag>baz</läufer:tag>
\ No newline at end of file
+<Läufer>foo</Läufer>
+<tag läufer="läufer">bar</tag>
+<läufer:tag>baz</läufer:tag>
diff --git a/examples/prism-mel.html b/examples/prism-mel.html
index eaf3ba517f..a42cf884a1 100644
--- a/examples/prism-mel.html
+++ b/examples/prism-mel.html
@@ -28,14 +28,14 @@ Arrays, vectors and matrices
print($array[1]); // Prints "second\n"
print($array[2]); // Prints "third\n"
-vector $roger = <<3.0, 7.7, 9.1>>;
-vector $more = <<4.5, 6.789, 9.12356>>;
+vector $roger = <<3.0, 7.7, 9.1>>;
+vector $more = <<4.5, 6.789, 9.12356>>;
// Assign a vector to variable $test:
-vector $test = <<3.0, 7.7, 9.1>>;
+vector $test = <<3.0, 7.7, 9.1>>;
$test = <<$test.x, 5.5, $test.z>>
-// $test is now <<3.0, 5.5, 9.1>>
+// $test is now <<3.0, 5.5, 9.1>>
-matrix $a3[3][4] = <<2.5, 4.5, 3.25, 8.05;
+matrix $a3[3][4] = <<2.5, 4.5, 3.25, 8.05;
1.12, 1.3, 9.5, 5.2;
7.23, 6.006, 2.34, 4.67>>
diff --git a/examples/prism-opencl.html b/examples/prism-opencl.html
index 92d54222e2..6bfb735703 100644
--- a/examples/prism-opencl.html
+++ b/examples/prism-opencl.html
@@ -7,14 +7,14 @@ OpenCL host code
// OpenCL functions, constants, etc. are also highlighted in OpenCL host code in the c or cpp language
cl::Event KernelFilterImages::runSingle(const cl::Image2D& imgSrc, SPImage2D& imgDst)
{
- const size_t rows = imgSrc.getImageInfo();
- const size_t cols = imgSrc.getImageInfo();
+ const size_t rows = imgSrc.getImageInfo<CL_IMAGE_HEIGHT>();
+ const size_t cols = imgSrc.getImageInfo<CL_IMAGE_WIDTH>();
ASSERT(rows > 0 && cols > 0, "The image object seems to be invalid, no rows/cols set");
- ASSERT(imgSrc.getImageInfo().image_channel_data_type == CL_FLOAT, "Only float type images are supported");
- ASSERT(imgSrc.getInfo() == CL_MEM_READ_ONLY || imgSrc.getInfo() == CL_MEM_READ_WRITE, "Can't read the input image");
+ ASSERT(imgSrc.getImageInfo<CL_IMAGE_FORMAT>().image_channel_data_type == CL_FLOAT, "Only float type images are supported");
+ ASSERT(imgSrc.getInfo<CL_MEM_FLAGS>() == CL_MEM_READ_ONLY || imgSrc.getInfo<CL_MEM_FLAGS>() == CL_MEM_READ_WRITE, "Can't read the input image");
- imgDst = std::make_shared(*context, CL_MEM_READ_WRITE, cl::ImageFormat(CL_R, CL_FLOAT), cols, rows);
+ imgDst = std::make_shared<cl::Image2D>(*context, CL_MEM_READ_WRITE, cl::ImageFormat(CL_R, CL_FLOAT), cols, rows);
cl::Kernel kernel(*program, "filter_single");
kernel.setArg(0, imgSrc);
diff --git a/examples/prism-q.html b/examples/prism-q.html
index 9d7d5c6226..85673d3abc 100644
--- a/examples/prism-q.html
+++ b/examples/prism-q.html
@@ -58,8 +58,8 @@ Dates
Verbs
99+L
-x<42|x>98
-(x<42)|x>98
+x<42|x>98
+(x<42)|x>98
42~(4 2;(1 0))
(4 2)~(4; 2*1)
diff --git a/examples/prism-renpy.html b/examples/prism-renpy.html
index dffac07c4b..746d215b32 100644
--- a/examples/prism-renpy.html
+++ b/examples/prism-renpy.html
@@ -1,21 +1,16 @@
Comments
-
- # This is a comment
-
+# This is a comment
Strings
-
- "foo \"bar\" baz"
+"foo \"bar\" baz"
'foo \'bar\' baz'
""" "Multi-line" strings
are supported."""
''' 'Multi-line' strings
-are supported.'''
-
+are supported.'''
Python
-
- class Dog:
+class Dog:
tricks = [] # mistaken use of a class variable
@@ -23,19 +18,15 @@ Python
self.name = name
def add_trick(self, trick):
- self.tricks.append(trick)
-
+ self.tricks.append(trick)
Properties
-
- style my_text is text:
+style my_text is text:
size 40
- font "gentium.ttf"
-
+ font "gentium.ttf"
Configuration
-
- init -1:
+init -1:
python hide:
## Should we enable the use of developer tools? This should be
@@ -52,8 +43,7 @@ Configuration
## This controls the title of the window, when Ren'Py is
## running in a window.
- config.window_title = u"The Question"
-
+ config.window_title = u"The Question"
Full example
@@ -120,4 +110,4 @@ Full example
"... to ask her later.":
- jump later
\ No newline at end of file
+ jump later
diff --git a/examples/prism-soy.html b/examples/prism-soy.html
index 648b8e5f91..9b43ceb75b 100644
--- a/examples/prism-soy.html
+++ b/examples/prism-soy.html
@@ -11,7 +11,7 @@ Variable
Commands
{template .helloNames}
// Greet the person.
- {call .helloName data="all" /}
+ {call .helloName data="all" /}<br>
// Greet the additional people.
{foreach $additionalName in $additionalNames}
{call .helloName}
@@ -33,4 +33,4 @@ Functions and print directives
Literal section
{literal}
This is not a {$variable}
-{/literal}
\ No newline at end of file
+{/literal}
diff --git a/examples/prism-sparql.html b/examples/prism-sparql.html
index 5dbcdf233b..f416c8598c 100644
--- a/examples/prism-sparql.html
+++ b/examples/prism-sparql.html
@@ -1,6 +1,7 @@
Introduction
-The queries shown here can be found in the SPARQL specifications:
-https://www.w3.org/TR/sparql11-query/
+
+The queries shown here can be found in the SPARQL specifications:
+https://www.w3.org/TR/sparql11-query/
query 2.1.6 Examples of Query Syntax
@@ -269,7 +270,7 @@ query 11.6-q1 Extensible Value Testing
}
-The final example query is not based on the SPARQL 1.1 queries.
+The final example query is not based on the SPARQL 1.1 queries.
1..48 +1..48 ok 1 Description # Directive # Diagnostic .... diff --git a/examples/prism-vala.html b/examples/prism-vala.html index ef5e98a921..456bdce2af 100644 --- a/examples/prism-vala.html +++ b/examples/prism-vala.html @@ -1,10 +1,10 @@
Comments
-// Single line comment +
// Single line comment /** Multi-line doc comment */
Strings
-"foo \"bar\" baz" +
"foo \"bar\" baz" "Multi-line strings ending with a \ are supported too." """Verbatim strings @@ -13,10 +13,10 @@
Strings
@"Template string with variables $var1 $(var2 * 2)"Regex
-+/foo?[ ]*bar/
/foo?[ ]*bar/
Full example
-diff --git a/gulpfile.js/premerge.js b/gulpfile.js/premerge.js index d674b2c5b2..c83894b102 100644 --- a/gulpfile.js/premerge.js +++ b/gulpfile.js/premerge.js @@ -1,10 +1,6 @@ "use strict"; -const { parallel } = require('gulp'); -const fs = require('fs'); const git = require('simple-git/promise')(__dirname); -// use the JSON file because this file is less susceptible to merge conflicts -const { languages } = require('../components.json'); /** @@ -19,46 +15,6 @@ function gitChanges() { }); } -/** - * Checks that all languages have and example. - */ -async function hasExample() { - const exampleFiles = new Set(fs.readdirSync(__dirname + '/../examples')); - const ignore = new Set([ - // these are libraries and not languages - 'markup-templating', - 't4-templating', - // this does alter some languages but it's mainly a library - 'javadoclike', - // Regex doesn't have any classes supported by our themes and mainly extends other languages - 'regex' - ]); - - /** @type {string[]} */ - const missing = []; - for (const lang in languages) { - if (lang === 'meta') { - continue; - } - - if (!exampleFiles.delete(`prism-${lang}.html`)) { - if (!ignore.has(lang)) { - missing.push(lang); - } - } - } - - const errors = missing.map(id => `Missing example for ${id}.`); - for (const file of exampleFiles) { - errors.push(`The examples file "${file}" has no language associated with it.`); - } - - if (errors.length) { - throw new Error(errors.join('\n')); - } -} - - module.exports = { - premerge: parallel(gitChanges, hasExample) + premerge: gitChanges }; diff --git a/package-lock.json b/package-lock.json index e9671a6339..eade03c9a5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -994,6 +994,22 @@ "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, + "dom-serializer": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.2.2.tgz", + "integrity": "sha512-2/xPb3ORsQ42nHYiSunXkDjPLBaEj/xTwUO4B7XCZQTRk7EBtTOPaygh10YAAh2OI1Qrp6NWfpAhzswj0ydt9g==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "entities": "^2.0.0" + } + }, + "domelementtype": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.0.1.tgz", + "integrity": "sha512-5HOHUDsYZWV8FGWN0Njbr/Rn7f/eWSQi1v7+HsUVwXgn8nWWlL64zKDkS0n8ZmQ3mlWOMuXOnR+7Nx/5tMO5AQ==", + "dev": true + }, "domexception": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", @@ -1003,6 +1019,26 @@ "webidl-conversions": "^4.0.2" } }, + "domhandler": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-3.0.0.tgz", + "integrity": "sha512-eKLdI5v9m67kbXQbJSNn1zjh0SDzvzWVWtX+qEI3eMjZw8daH9k8rlj1FZY9memPwjiskQFbe7vHVVJIAqoEhw==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1" + } + }, + "domutils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.0.0.tgz", + "integrity": "sha512-n5SelJ1axbO636c2yUtOGia/IcJtVtlhQbFiVDBZHKV5ReJO1ViX7sFEemtuyoAnBxk5meNSYgA8V4s0271efg==", + "dev": true, + "requires": { + "dom-serializer": "^0.2.1", + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0" + } + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -1056,6 +1092,12 @@ "once": "^1.4.0" } }, + "entities": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-2.0.0.tgz", + "integrity": "sha512-D9f7V0JSRwIxlRI2mjMqufDrRDnx8p+eEOz7aUM9SuvF8gsBzra0/6tbjl1m8eQHrZlYj6PxqE00hZ1SAIKPLw==", + "dev": true + }, "error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -2496,6 +2538,18 @@ "whatwg-encoding": "^1.0.1" } }, + "htmlparser2": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-4.0.0.tgz", + "integrity": "sha512-cChwXn5Vam57fyXajDtPXL1wTYc8JtLbr2TN76FYu05itVVVealxLowe2B3IEznJG4p9HAYn/0tJaRlGuEglFQ==", + "dev": true, + "requires": { + "domelementtype": "^2.0.1", + "domhandler": "^3.0.0", + "domutils": "^2.0.0", + "entities": "^2.0.0" + } + }, "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", diff --git a/package.json b/package.json index 0a85c9499c..399285cb3d 100755 --- a/package.json +++ b/package.json @@ -8,11 +8,12 @@ "test:aliases": "mocha tests/aliases-test.js", "test:core": "mocha tests/core/**/*.js", "test:dependencies": "mocha tests/dependencies-test.js", + "test:examples": "mocha tests/examples-test.js", "test:languages": "mocha tests/run.js", "test:patterns": "mocha tests/pattern-tests.js", "test:plugins": "mocha tests/plugins/**/*.js", "test:runner": "mocha tests/testrunner-tests.js", - "test": "npm run test:runner && npm run test:core && npm run test:dependencies && npm run test:languages && npm run test:plugins && npm run test:aliases && npm run test:patterns" + "test": "npm run test:runner && npm run test:core && npm run test:dependencies && npm run test:languages && npm run test:plugins && npm run test:aliases && npm run test:patterns && npm run test:examples" }, "repository": { "type": "git", @@ -36,6 +37,7 @@ "gulp-rename": "^1.2.0", "gulp-replace": "^1.0.0", "gulp-uglify": "^3.0.1", + "htmlparser2": "^4.0.0", "jsdom": "^13.0.0", "mocha": "^6.2.0", "pump": "^3.0.0", diff --git a/tests/examples-test.js b/tests/examples-test.js new file mode 100644 index 0000000000..b45c684917 --- /dev/null +++ b/tests/examples-test.js @@ -0,0 +1,187 @@ +const fs = require('fs'); +const { assert } = require('chai'); +const { Parser } = require('htmlparser2'); +// use the JSON file because this file is less susceptible to merge conflicts +const { languages } = require('../components.json'); + + +describe('Examples', function () { + + const exampleFiles = new Set(fs.readdirSync(__dirname + '/../examples')); + const ignore = new Set([ + // these are libraries and not languages + 'markup-templating', + 't4-templating', + // this does alter some languages but it's mainly a library + 'javadoclike', + // Regex doesn't have any classes supported by our themes and mainly extends other languages + 'regex' + ]); + const validFiles = new Set(); + + /** @type {string[]} */ + const missing = []; + for (const lang in languages) { + if (lang === 'meta') { + continue; + } + + const file = `prism-${lang}.html`; + if (!exampleFiles.has(file)) { + if (!ignore.has(lang)) { + missing.push(lang); + } + } else { + validFiles.add(file); + } + } + + const superfluous = [...exampleFiles].filter(f => !validFiles.has(f)); + + + it('- should be available for every language', function () { + assert.isEmpty(missing, 'Following languages do not have an example file in ./examples/\n' + + missing.join('\n')); + }); + + it('- should only be available for registered languages', function () { + assert.isEmpty(superfluous, 'Following files are not associated with any language\n' + + superfluous.map(f => `./examples/${f}`).join('\n')); + }); + + describe('Validate HTML templates', function () { + for (const file of validFiles) { + it('- ./examples/' + file, async function () { + const content = fs.readFileSync(__dirname + '/../examples/' + file, 'utf-8'); + await validateHTML(content); + }); + } + }); + +}); + + +/** + * Validates the given HTML string of an example file. + * + * @param {string} html + */ +async function validateHTML(html) { + const root = await parseHTML(html); + + /** + * @param {TagNode} node + */ + function checkCodeElements(node) { + if (node.tagName === 'code') { + assert.equal(node.children.length, 1, + 'Ausing Gtk; +
\ No newline at end of file +}using Gtk; int main (string[] args) { Gtk.init(ref args); @@ -30,4 +30,4 @@
Full example
Gtk.main(); return 0; -}element is only allowed to contain text, no tags. ' + + 'Did you perhaps not escape all "<" characters?'); + + const child = node.children[0]; + if (child.type !== 'text') { + // throw to help TypeScript's flow analysis + throw assert.equal(child.type, 'text', 'The child of a
element must be text only.'); + } + + const text = child.rawText; + + assert.notMatch(text, /, 'All "<" characters have to be escape with "<".'); + assert.notMatch(text, /&(?!amp;|lt;|gt;)(?:[#\w]+);/, 'Only certain entities are allowed.'); + } else { + node.children.forEach(n => { + if (n.type === 'tag') { + checkCodeElements(n); + } + }); + } + } + + for (const node of root.children) { + if (node.type === 'text') { + assert.isEmpty(node.rawText.trim(), 'All non-whitespace text has to be in
tags.'); + } else { + // only known tags + assert.match(node.tagName, /^(?:h2|h3|p|pre|ul|ol)$/, 'Only some tags are allowed as top level tags.'); + + //
elements must have only one child, aelement + if (node.tagName === 'pre') { + assert.equal(node.children.length, 1, + '
element must have one and only one child node, aelement.' + + ' This also means that spaces and line breaks around the
element are not allowed.'); + + const child = node.children[0]; + if (child.type !== 'tag') { + // throw to help TypeScript's flow analysis + throw assert.equal(child.type, 'tag', 'The child of a
element must be aelement.'); + } + assert.equal(child.tagName, 'code', 'The child of a
element must be aelement.'); + } + + checkCodeElements(node); + } + } +} + +/** + * Parses the given HTML fragment and returns a simple tree of the fragment. + * + * @param {string} html + * @returns {Promise
} + * + * @typedef TagNode + * @property {"tag"} type + * @property {string | null} tagName + * @property {Object } attributes + * @property {(TagNode | TextNode)[]} children + * + * @typedef TextNode + * @property {"text"} type + * @property {string} rawText + */ +function parseHTML(html) { + return new Promise((resolve, reject) => { + /** @type {TagNode} */ + const tree = { + type: 'tag', + tagName: null, + attributes: {}, + children: [] + }; + /** @type {TagNode[]} */ + let stack = [tree]; + + const p = new Parser({ + onerror(err) { + reject(err) + }, + onend() { + resolve(tree); + }, + + ontext(data) { + stack[stack.length - 1].children.push({ + type: 'text', + rawText: data + }); + }, + + onopentag(name, attrs) { + /** @type {TagNode} */ + const newElement = { + type: 'tag', + tagName: name, + attributes: attrs, + children: [] + }; + stack[stack.length - 1].children.push(newElement); + stack.push(newElement); + }, + onclosetag() { + stack.pop(); + } + + }, { lowerCaseTags: false }); + p.end(html); + }); +}