Added tests for examples (#2216)

This adds a new test to validate the structure and syntax of all examples and moves the existing example test into the mocha test suite.
This commit is contained in:
Michael Schmidt 2020-03-07 11:53:31 +01:00 committed by GitHub
parent 26626dedad
commit 1f7a245c44
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 352 additions and 163 deletions

View File

@ -1,11 +1,11 @@
<h2>Comments</h2>
<pre><code>
* Line Comments
&quot; End of line comment used as line comment.
value = 1. &quot; End of line comment
" End of line comment used as line comment.
value = 1. " End of line comment
DATA:
&quot;! ABAPDoc comment
"! ABAPDoc comment
value TYPE i.
</code></pre>
@ -16,7 +16,7 @@ my_string = 'Simple string'.
my_string = 'String with an escaped '' inside'.
my_string = |A string template: { nvalue } times|.
my_string = |A string template: { nvalue } times|.
my_string = |Characters \|, \{, and \} have to be escaped by \\ in literal text.|.
my_string = |Characters \|, \{, and \} have to be escaped by \\ in literal text.|.
</code></pre>
<h2>Numbers and Operators</h2>
@ -62,4 +62,4 @@ DATA lo_instace TYPE REF TO lcl_my_class.
CREATE OBJECT lo_instace.
my_structure-component = lo_instace->my_method( ).
</code></pre>
</code></pre>

View File

@ -55,100 +55,100 @@ export {
# Whether to consider UDP "connections" for scan detection.
# Can lead to false positives due to UDP fanout from some P2P apps.
const suppress_UDP_scan_checks = F &redef;
const suppress_UDP_scan_checks = F &amp;redef;
const activate_priv_port_check = T &redef;
const activate_landmine_check = F &redef;
const landmine_thresh_trigger = 5 &redef;
const activate_priv_port_check = T &amp;redef;
const activate_landmine_check = F &amp;redef;
const landmine_thresh_trigger = 5 &amp;redef;
const landmine_address: set[addr] &redef;
const landmine_address: set[addr] &amp;redef;
const scan_summary_trigger = 25 &redef;
const port_summary_trigger = 20 &redef;
const lowport_summary_trigger = 10 &redef;
const scan_summary_trigger = 25 &amp;redef;
const port_summary_trigger = 20 &amp;redef;
const lowport_summary_trigger = 10 &amp;redef;
# Raise ShutdownThresh after this many failed attempts
const shut_down_thresh = 100 &redef;
const shut_down_thresh = 100 &amp;redef;
# Which services should be analyzed when detecting scanning
# (not consulted if analyze_all_services is set).
const analyze_services: set[port] &redef;
const analyze_all_services = T &redef;
const analyze_services: set[port] &amp;redef;
const analyze_all_services = T &amp;redef;
# Track address scaners only if at least these many hosts contacted.
const addr_scan_trigger = 0 &redef;
const addr_scan_trigger = 0 &amp;redef;
# Ignore address scanners for further scan detection after
# scanning this many hosts.
# 0 disables.
const ignore_scanners_threshold = 0 &redef;
const ignore_scanners_threshold = 0 &amp;redef;
# Report a scan of peers at each of these points.
const report_peer_scan: vector of count = {
20, 100, 1000, 10000, 50000, 100000, 250000, 500000, 1000000,
} &redef;
} &amp;redef;
const report_outbound_peer_scan: vector of count = {
100, 1000, 10000,
} &redef;
} &amp;redef;
# Report a scan of ports at each of these points.
const report_port_scan: vector of count = {
50, 250, 1000, 5000, 10000, 25000, 65000,
} &redef;
} &amp;redef;
# Once a source has scanned this many different ports (to however many
# different remote hosts), start tracking its per-destination access.
const possible_port_scan_thresh = 20 &redef;
const possible_port_scan_thresh = 20 &amp;redef;
# Threshold for scanning privileged ports.
const priv_scan_trigger = 5 &redef;
const priv_scan_trigger = 5 &amp;redef;
const troll_skip_service = {
25/tcp, 21/tcp, 22/tcp, 20/tcp, 80/tcp,
} &redef;
} &amp;redef;
const report_accounts_tried: vector of count = {
20, 100, 1000, 10000, 100000, 1000000,
} &redef;
} &amp;redef;
const report_remote_accounts_tried: vector of count = {
100, 500,
} &redef;
} &amp;redef;
# Report a successful password guessing if the source attempted
# at least this many.
const password_guessing_success_threshhold = 20 &redef;
const password_guessing_success_threshhold = 20 &amp;redef;
const skip_accounts_tried: set[addr] &redef;
const skip_accounts_tried: set[addr] &amp;redef;
const addl_web = {
81/tcp, 443/tcp, 8000/tcp, 8001/tcp, 8080/tcp, }
&redef;
&amp;redef;
const skip_services = { 113/tcp, } &redef;
const skip_services = { 113/tcp, } &amp;redef;
const skip_outbound_services = { 21/tcp, addl_web, }
&redef;
&amp;redef;
const skip_scan_sources = {
255.255.255.255, # who knows why we see these, but we do
} &redef;
} &amp;redef;
const skip_scan_nets: set[subnet] = {} &redef;
const skip_scan_nets: set[subnet] = {} &amp;redef;
# List of well known local server/ports to exclude for scanning
# purposes.
const skip_dest_server_ports: set[addr, port] = {} &redef;
const skip_dest_server_ports: set[addr, port] = {} &amp;redef;
# Reverse (SYN-ack) scans seen from these ports are considered
# to reflect possible SYN-flooding backscatter, and not true
# (stealth) scans.
const backscatter_ports = {
80/tcp, 8080/tcp, 53/tcp, 53/udp, 179/tcp, 6666/tcp, 6667/tcp,
} &redef;
} &amp;redef;
const report_backscatter: vector of count = {
20,
} &redef;
} &amp;redef;
global check_scan:
function(c: connection, established: bool, reverse: bool): bool;
@ -174,14 +174,14 @@ export {
# Indexed by scanner address, yields # distinct peers scanned.
# pre_distinct_peers tracks until addr_scan_trigger hosts first.
global pre_distinct_peers: table[addr] of set[addr]
&read_expire = 15 mins &redef;
&read_expire = 15 mins &amp;redef;
global distinct_peers: table[addr] of set[addr]
&read_expire = 15 mins &expire_func=scan_summary &redef;
&read_expire = 15 mins &expire_func=scan_summary &amp;redef;
global distinct_ports: table[addr] of set[port]
&read_expire = 15 mins &expire_func=port_summary &redef;
&read_expire = 15 mins &expire_func=port_summary &amp;redef;
global distinct_low_ports: table[addr] of set[port]
&read_expire = 15 mins &expire_func=lowport_summary &redef;
&read_expire = 15 mins &expire_func=lowport_summary &amp;redef;
# Indexed by scanner address, yields a table with scanned hosts
# (and ports).
@ -196,23 +196,23 @@ export {
global accounts_tried: table[addr] of set[string, string]
&read_expire = 1 days;
global ignored_scanners: set[addr] &create_expire = 1 day &redef;
global ignored_scanners: set[addr] &create_expire = 1 day &amp;redef;
# These tables track whether a threshold has been reached.
# More precisely, the counter is the next index of threshold vector.
global shut_down_thresh_reached: table[addr] of bool &default=F;
global rb_idx: table[addr] of count
&default=1 &read_expire = 1 days &redef;
&default=1 &read_expire = 1 days &amp;redef;
global rps_idx: table[addr] of count
&default=1 &read_expire = 1 days &redef;
&default=1 &read_expire = 1 days &amp;redef;
global rops_idx: table[addr] of count
&default=1 &read_expire = 1 days &redef;
&default=1 &read_expire = 1 days &amp;redef;
global rpts_idx: table[addr,addr] of count
&default=1 &read_expire = 1 days &redef;
&default=1 &read_expire = 1 days &amp;redef;
global rat_idx: table[addr] of count
&default=1 &read_expire = 1 days &redef;
&default=1 &read_expire = 1 days &amp;redef;
global rrat_idx: table[addr] of count
&default=1 &read_expire = 1 days &redef;
&default=1 &read_expire = 1 days &amp;redef;
}
global thresh_check: function(v: vector of count, idx: table[addr] of count,
@ -388,7 +388,7 @@ function check_scan(c: connection, established: bool, reverse: bool): bool
{ # XXXXX
if ( orig !in distinct_peers )
distinct_peers[orig] = set() &mergeable;
distinct_peers[orig] = set() &amp;mergeable;
if ( resp !in distinct_peers[orig] )
add distinct_peers[orig][resp];
@ -448,7 +448,7 @@ function check_scan(c: connection, established: bool, reverse: bool): bool
if ( orig !in distinct_ports || service !in distinct_ports[orig] )
{
if ( orig !in distinct_ports )
distinct_ports[orig] = set() &mergeable;
distinct_ports[orig] = set() &amp;mergeable;
if ( service !in distinct_ports[orig] )
add distinct_ports[orig][service];
@ -456,7 +456,7 @@ function check_scan(c: connection, established: bool, reverse: bool): bool
if ( |distinct_ports[orig]| >= possible_port_scan_thresh &&
orig !in scan_triples )
{
scan_triples[orig] = table() &mergeable;
scan_triples[orig] = table() &amp;mergeable;
add possible_scan_sources[orig];
}
}
@ -469,7 +469,7 @@ function check_scan(c: connection, established: bool, reverse: bool): bool
service !in distinct_low_ports[orig] )
{
if ( orig !in distinct_low_ports )
distinct_low_ports[orig] = set() &mergeable;
distinct_low_ports[orig] = set() &amp;mergeable;
add distinct_low_ports[orig][service];
@ -494,10 +494,10 @@ function check_scan(c: connection, established: bool, reverse: bool): bool
if ( orig in possible_scan_sources )
{
if ( orig !in scan_triples )
scan_triples[orig] = table() &mergeable;
scan_triples[orig] = table() &amp;mergeable;
if ( resp !in scan_triples[orig] )
scan_triples[orig][resp] = set() &mergeable;
scan_triples[orig][resp] = set() &amp;mergeable;
if ( service !in scan_triples[orig][resp] )
{

View File

@ -334,14 +334,14 @@ keymap ; => {:a 1, :b 2, :c 3}
java.util.Calendar))
; Use the class name with a "." at the end to make a new instance
(Date.) ; <a date object>
(Date.) ; &lt;a date object>
; Use . to call methods. Or, use the ".method" shortcut
(. (Date.) getTime) ; <a timestamp>
(. (Date.) getTime) ; &lt;a timestamp>
(.getTime (Date.)) ; exactly the same thing.
; Use / to call static methods
(System/currentTimeMillis) ; <a timestamp> (system is always present)
(System/currentTimeMillis) ; &lt;a timestamp> (system is always present)
; Use doto to make dealing with (mutable) classes more tolerable
(import java.util.Calendar)

View File

@ -65,10 +65,10 @@ def sum(elms int...) int {
thesum = sum(1, 2, 3, 4, 5)
//partially defined typedef
typedef NameMap<X> = java.util.ArrayList<java.util.HashMap<String, java.util.HashSet<X>>>
typedef NameMap&lt;X> = java.util.ArrayList&lt;java.util.HashMap&lt;String, java.util.HashSet&lt;X>>>
//using typedefs...
nm NameMap<String>= new NameMap<String>()
nm NameMap&lt;String>= new NameMap&lt;String>()
@Annotation
class MyClass(a int, b int, c String){
@ -79,11 +79,11 @@ mc1 = MyClass(12, 14, "hi there")
mc2 = mc1@ //copy mc1
assert mc1 == mc2//same values!
assert mc1 &<> mc2//different objects!
assert mc1 &&lt;> mc2//different objects!
mc3 = mc1@(a = 100)//copy mc1 but overwrite value of a
assert 'MyClass(100, 14, "hi there")' == mc3.toString()
mc4 = mc1@(<a, b>)//copy mc1 but exclude a and b
mc4 = mc1@(&lt;a, b>)//copy mc1 but exclude a and b
assert 'MyClass(0, 0, "hi there")' == mc3.toString()
</code></pre>

View File

@ -10,7 +10,7 @@
# VERSION 0.0.1
FROM ubuntu
MAINTAINER Victor Vieux <victor@docker.com>
MAINTAINER Victor Vieux &lt;victor@docker.com>
LABEL Description="This image is used to start the foobar executable" Vendor="ACME Products" Version="1.0"
RUN apt-get update && apt-get install -y inotify-tools nginx apache2 openssh-server

View File

@ -55,8 +55,7 @@ BV{ 1 2 3 4 }</code></pre>
<h2>Regular Expressions</h2>
<pre><code>USE: regexp
R/ abcde?.*+\?\.\*\+\/\\\/idmsr-idmsr/idmsr-idmsr</code>
</pre>
R/ abcde?.*+\?\.\*\+\/\\\/idmsr-idmsr/idmsr-idmsr</code></pre>
<h2>Colon parsing words</h2>
<pre><code>: a ( -- ) ;
@ -73,9 +72,9 @@ GENERIC#: x 1 ( x: integer quot: ( x -- y ) -- )</code></pre>
new last-index + - neg
&lt;array&gt; <=> SYNTAX: x $[ xyz ]
&lt;array&gt; &lt;=> 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&lt;&lt;
string>number >hex base> mutater!
</code></pre>

View File

@ -3,7 +3,7 @@
<h2>Control Flow</h2>
Liquid provides multiple control flow statements.
<p>Liquid provides multiple control flow statements.</p>
<h3>if</h3>
<pre><code>
@ -18,7 +18,7 @@ Liquid provides multiple control flow statements.
<h3>unless</h3>
The opposite of <code>if</code> executes a block of code only if a certain condition is not met.
<p>The opposite of <code>if</code> executes a block of code only if a certain condition is not met.</p>
<pre><code>
{% unless product.title == 'Awesome Shoes' %}
@ -28,7 +28,7 @@ These shoes are not awesome.
<h3>case</h3>
Creates a switch statement to compare a variable with different values. <code>case</code> initializes the switch statement, and <code>when</code> compares its values.
<p>Creates a switch statement to compare a variable with different values. <code>case</code> initializes the switch statement, and <code>when</code> compares its values.</p>
<pre><code>
{% assign handle = 'cake' %}
@ -44,10 +44,10 @@ Creates a switch statement to compare a variable with different values. <code>ca
<h3>for</h3>
Repeatedly executes a block of code.
<p>Repeatedly executes a block of code.</p>
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.
<p>break = Causes the loop to stop iterating when it encounters the break tag. <br>
continue = Causes the loop to skip the current iteration when it encounters the continue tag.</p>
<pre><code>
{% for i in (1..10) %}

View File

@ -72,6 +72,6 @@ bar
baz"></code></pre>
<h2>XML tags with non-ASCII characters</h2>
<pre><code>&lt;L&auml;ufer&gt;foo&lt;/L&auml;ufer&gt;
&lt;tag l&auml;ufer="l&auml;ufer"&gt;bar&lt;/tag&gt;
&lt;l&auml;ufer:tag&gt;baz&lt;/l&auml;ufer:tag&gt;</code></pre>
<pre><code>&lt;Läufer&gt;foo&lt;/Läufer&gt;
&lt;tag läufer="läufer"&gt;bar&lt;/tag&gt;
&lt;läufer:tag&gt;baz&lt;/läufer:tag&gt;</code></pre>

View File

@ -28,14 +28,14 @@ print($array[0]); // Prints "first\n"
print($array[1]); // Prints "second\n"
print($array[2]); // Prints "third\n"
vector $roger = &lt;<3.0, 7.7, 9.1>>;
vector $more = &lt;<4.5, 6.789, 9.12356>>;
vector $roger = &lt;&lt;3.0, 7.7, 9.1>>;
vector $more = &lt;&lt;4.5, 6.789, 9.12356>>;
// Assign a vector to variable $test:
vector $test = &lt;<3.0, 7.7, 9.1>>;
vector $test = &lt;&lt;3.0, 7.7, 9.1>>;
$test = &lt;&lt;$test.x, 5.5, $test.z>>
// $test is now &lt;<3.0, 5.5, 9.1>>
// $test is now &lt;&lt;3.0, 5.5, 9.1>>
matrix $a3[3][4] = &lt;<2.5, 4.5, 3.25, 8.05;
matrix $a3[3][4] = &lt;&lt;2.5, 4.5, 3.25, 8.05;
1.12, 1.3, 9.5, 5.2;
7.23, 6.006, 2.34, 4.67>></code></pre>

View File

@ -7,14 +7,14 @@
<pre class="language-cpp"><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<CL_IMAGE_HEIGHT>();
const size_t cols = imgSrc.getImageInfo<CL_IMAGE_WIDTH>();
const size_t rows = imgSrc.getImageInfo&lt;CL_IMAGE_HEIGHT>();
const size_t cols = imgSrc.getImageInfo&lt;CL_IMAGE_WIDTH>();
ASSERT(rows > 0 && cols > 0, "The image object seems to be invalid, no rows/cols set");
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");
ASSERT(imgSrc.getImageInfo&lt;CL_IMAGE_FORMAT>().image_channel_data_type == CL_FLOAT, "Only float type images are supported");
ASSERT(imgSrc.getInfo&lt;CL_MEM_FLAGS>() == CL_MEM_READ_ONLY || imgSrc.getInfo&lt;CL_MEM_FLAGS>() == CL_MEM_READ_WRITE, "Can't read the input image");
imgDst = std::make_shared<cl::Image2D>(*context, CL_MEM_READ_WRITE, cl::ImageFormat(CL_R, CL_FLOAT), cols, rows);
imgDst = std::make_shared&lt;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);

View File

@ -58,8 +58,8 @@ d:2006.07.04
<h2>Verbs</h2>
<pre><code>99+L
x<42|x>98
(x<42)|x>98
x&lt;42|x>98
(x&lt;42)|x>98
42~(4 2;(1 0))
(4 2)~(4; 2*1)</code></pre>

View File

@ -1,21 +1,16 @@
<h2>Comments</h2>
<pre>
<code># This is a comment</code>
</pre>
<pre><code># This is a comment</code></pre>
<h2>Strings</h2>
<pre>
<code>"foo \"bar\" baz"
<pre><code>"foo \"bar\" baz"
'foo \'bar\' baz'
""" "Multi-line" strings
are supported."""
''' 'Multi-line' strings
are supported.'''</code>
</pre>
are supported.'''</code></pre>
<h2>Python</h2>
<pre>
<code>class Dog:
<pre><code>class Dog:
tricks = [] # mistaken use of a class variable
@ -23,19 +18,15 @@ are supported.'''</code>
self.name = name
def add_trick(self, trick):
self.tricks.append(trick)</code>
</pre>
self.tricks.append(trick)</code></pre>
<h2>Properties</h2>
<pre>
<code>style my_text is text:
<pre><code>style my_text is text:
size 40
font "gentium.ttf"</code>
</pre>
font "gentium.ttf"</code></pre>
<h2>Configuration</h2>
<pre>
<code>init -1:
<pre><code>init -1:
python hide:
## Should we enable the use of developer tools? This should be
@ -52,8 +43,7 @@ are supported.'''</code>
## This controls the title of the window, when Ren'Py is
## running in a window.
config.window_title = u"The Question"</code>
</pre>
config.window_title = u"The Question"</code></pre>
<h2>Full example</h2>
@ -120,4 +110,4 @@ label start:
"... to ask her later.":
jump later</code></pre>
jump later</code></pre>

View File

@ -11,7 +11,7 @@ comment */
<h2>Commands</h2>
<pre><code>{template .helloNames}
// Greet the person.
{call .helloName data="all" /}<br>
{call .helloName data="all" /}&lt;br>
// Greet the additional people.
{foreach $additionalName in $additionalNames}
{call .helloName}
@ -33,4 +33,4 @@ comment */
<h2>Literal section</h2>
<pre><code>{literal}
This is not a {$variable}
{/literal}</code></pre>
{/literal}</code></pre>

View File

@ -1,6 +1,7 @@
<h2>Introduction</h2>
The queries shown here can be found in the SPARQL specifications:
<a href="https://www.w3.org/TR/sparql11-query/">https://www.w3.org/TR/sparql11-query/</a>
<p>The queries shown here can be found in the SPARQL specifications:
<a href="https://www.w3.org/TR/sparql11-query/">https://www.w3.org/TR/sparql11-query/</a></p>
<h2>query 2.1.6 Examples of Query Syntax</h2>
@ -269,7 +270,7 @@ FILTER regex(str(?mbox), "@work.example") }
}
</code></pre>
The final example query is not based on the SPARQL 1.1 queries.
<p>The final example query is not based on the SPARQL 1.1 queries.</p>
<h2>Full Example query</h2>

View File

@ -1,5 +1,5 @@
<h2>Full example</h2>
<pre></code>1..48
<pre><code>1..48
ok 1 Description # Directive
# Diagnostic
....

View File

@ -1,10 +1,10 @@
<h2>Comments</h2>
<pre><code">// Single line comment
<pre><code>// Single line comment
/** Multi-line
doc comment */</code></pre>
<h2>Strings</h2>
<pre><code">"foo \"bar\" baz"
<pre><code>"foo \"bar\" baz"
"Multi-line strings ending with a \
are supported too."
"""Verbatim strings
@ -13,10 +13,10 @@ multi-line strings like this too."""
@"Template string with variables $var1 $(var2 * 2)"</code></pre>
<h2>Regex</h2>
<pre><code">/foo?[ ]*bar/</code></pre>
<pre><code>/foo?[ ]*bar/</code></pre>
<h2>Full example</h2>
<pre><code">using Gtk;
<pre><code>using Gtk;
int main (string[] args) {
Gtk.init(ref args);
@ -30,4 +30,4 @@ int main (string[] args) {
Gtk.main();
return 0;
}</code></pre>
}</code></pre>

View File

@ -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
};

54
package-lock.json generated
View File

@ -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",

View File

@ -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",

187
tests/examples-test.js Normal file
View File

@ -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,
'A <code> 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 <code> element must be text only.');
}
const text = child.rawText;
assert.notMatch(text, /</, 'All "<" characters have to be escape with "&lt;".');
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 <p> tags.');
} else {
// only known tags
assert.match(node.tagName, /^(?:h2|h3|p|pre|ul|ol)$/, 'Only some tags are allowed as top level tags.');
// <pre> elements must have only one child, a <code> element
if (node.tagName === 'pre') {
assert.equal(node.children.length, 1,
'<pre> element must have one and only one child node, a <code> element.'
+ ' This also means that spaces and line breaks around the <code> 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 <pre> element must be a <code> element.');
}
assert.equal(child.tagName, 'code', 'The child of a <pre> element must be a <code> element.');
}
checkCodeElements(node);
}
}
}
/**
* Parses the given HTML fragment and returns a simple tree of the fragment.
*
* @param {string} html
* @returns {Promise<TagNode>}
*
* @typedef TagNode
* @property {"tag"} type
* @property {string | null} tagName
* @property {Object<string, string>} 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);
});
}