From 1f7a245c441d5aa07b5945e590356f0f7c39425e Mon Sep 17 00:00:00 2001 From: Michael Schmidt Date: Sat, 7 Mar 2020 11:53:31 +0100 Subject: [PATCH] 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. --- examples/prism-abap.html | 10 +- examples/prism-bro.html | 96 ++++++++--------- examples/prism-clojure.html | 6 +- examples/prism-concurnas.html | 8 +- examples/prism-docker.html | 2 +- examples/prism-factor.html | 7 +- examples/prism-liquid.html | 12 +-- examples/prism-markup.html | 6 +- examples/prism-mel.html | 10 +- examples/prism-opencl.html | 10 +- examples/prism-q.html | 4 +- examples/prism-renpy.html | 30 ++---- examples/prism-soy.html | 4 +- examples/prism-sparql.html | 7 +- examples/prism-tap.html | 2 +- examples/prism-vala.html | 10 +- gulpfile.js/premerge.js | 46 +-------- package-lock.json | 54 ++++++++++ package.json | 4 +- tests/examples-test.js | 187 ++++++++++++++++++++++++++++++++++ 20 files changed, 352 insertions(+), 163 deletions(-) create mode 100644 tests/examples-test.js 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 @@

Comments


 * 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 @@

Strings

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.|.

Numbers and Operators

@@ -62,4 +62,4 @@

Structures and Classes

CREATE OBJECT lo_instace. my_structure-component = lo_instace->my_method( ). - \ No newline at end of file + diff --git a/examples/prism-bro.html b/examples/prism-bro.html index d7645c6db5..86d3b3d9ea 100644 --- a/examples/prism-bro.html +++ b/examples/prism-bro.html @@ -55,100 +55,100 @@

Full example

# 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 &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 &redef; + const activate_landmine_check = F &redef; + const landmine_thresh_trigger = 5 &redef; - const landmine_address: set[addr] &redef; + const landmine_address: set[addr] &redef; - const scan_summary_trigger = 25 &redef; - const port_summary_trigger = 20 &redef; - const lowport_summary_trigger = 10 &redef; + const scan_summary_trigger = 25 &redef; + const port_summary_trigger = 20 &redef; + const lowport_summary_trigger = 10 &redef; # Raise ShutdownThresh after this many failed attempts - const shut_down_thresh = 100 &redef; + const shut_down_thresh = 100 &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] &redef; + const analyze_all_services = T &redef; # Track address scaners only if at least these many hosts contacted. - const addr_scan_trigger = 0 &redef; + const addr_scan_trigger = 0 &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 &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; + } &redef; const report_outbound_peer_scan: vector of count = { 100, 1000, 10000, - } &redef; + } &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; + } &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 &redef; # Threshold for scanning privileged ports. - const priv_scan_trigger = 5 &redef; + const priv_scan_trigger = 5 &redef; const troll_skip_service = { 25/tcp, 21/tcp, 22/tcp, 20/tcp, 80/tcp, - } &redef; + } &redef; const report_accounts_tried: vector of count = { 20, 100, 1000, 10000, 100000, 1000000, - } &redef; + } &redef; const report_remote_accounts_tried: vector of count = { 100, 500, - } &redef; + } &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 &redef; - const skip_accounts_tried: set[addr] &redef; + const skip_accounts_tried: set[addr] &redef; const addl_web = { 81/tcp, 443/tcp, 8000/tcp, 8001/tcp, 8080/tcp, } - &redef; + &redef; - const skip_services = { 113/tcp, } &redef; + const skip_services = { 113/tcp, } &redef; const skip_outbound_services = { 21/tcp, addl_web, } - &redef; + &redef; const skip_scan_sources = { 255.255.255.255, # who knows why we see these, but we do - } &redef; + } &redef; - const skip_scan_nets: set[subnet] = {} &redef; + const skip_scan_nets: set[subnet] = {} &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] = {} &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; + } &redef; const report_backscatter: vector of count = { 20, - } &redef; + } &redef; global check_scan: function(c: connection, established: bool, reverse: bool): bool; @@ -174,14 +174,14 @@

Full example

# 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 &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 &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 &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 &redef; # Indexed by scanner address, yields a table with scanned hosts # (and ports). @@ -196,23 +196,23 @@

Full example

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 &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 &redef; global rps_idx: table[addr] of count - &default=1 &read_expire = 1 days &redef; + &default=1 &read_expire = 1 days &redef; global rops_idx: table[addr] of count - &default=1 &read_expire = 1 days &redef; + &default=1 &read_expire = 1 days &redef; global rpts_idx: table[addr,addr] of count - &default=1 &read_expire = 1 days &redef; + &default=1 &read_expire = 1 days &redef; global rat_idx: table[addr] of count - &default=1 &read_expire = 1 days &redef; + &default=1 &read_expire = 1 days &redef; global rrat_idx: table[addr] of count - &default=1 &read_expire = 1 days &redef; + &default=1 &read_expire = 1 days &redef; } global thresh_check: function(v: vector of count, idx: table[addr] of count, @@ -388,7 +388,7 @@

Full example

{ # XXXXX if ( orig !in distinct_peers ) - distinct_peers[orig] = set() &mergeable; + distinct_peers[orig] = set() &mergeable; if ( resp !in distinct_peers[orig] ) add distinct_peers[orig][resp]; @@ -448,7 +448,7 @@

Full example

if ( orig !in distinct_ports || service !in distinct_ports[orig] ) { if ( orig !in distinct_ports ) - distinct_ports[orig] = set() &mergeable; + distinct_ports[orig] = set() &mergeable; if ( service !in distinct_ports[orig] ) add distinct_ports[orig][service]; @@ -456,7 +456,7 @@

Full example

if ( |distinct_ports[orig]| >= possible_port_scan_thresh && orig !in scan_triples ) { - scan_triples[orig] = table() &mergeable; + scan_triples[orig] = table() &mergeable; add possible_scan_sources[orig]; } } @@ -469,7 +469,7 @@

Full example

service !in distinct_low_ports[orig] ) { if ( orig !in distinct_low_ports ) - distinct_low_ports[orig] = set() &mergeable; + distinct_low_ports[orig] = set() &mergeable; add distinct_low_ports[orig][service]; @@ -494,10 +494,10 @@

Full example

if ( orig in possible_scan_sources ) { if ( orig !in scan_triples ) - scan_triples[orig] = table() &mergeable; + scan_triples[orig] = table() &mergeable; if ( resp !in scan_triples[orig] ) - scan_triples[orig][resp] = set() &mergeable; + scan_triples[orig][resp] = set() &mergeable; if ( service !in scan_triples[orig][resp] ) { diff --git a/examples/prism-clojure.html b/examples/prism-clojure.html index 4dd511816f..8e12fca893 100644 --- a/examples/prism-clojure.html +++ b/examples/prism-clojure.html @@ -334,14 +334,14 @@

Full example

java.util.Calendar)) ; Use the class name with a "." at the end to make a new instance -(Date.) ; +(Date.) ; <a date object> ; Use . to call methods. Or, use the ".method" shortcut -(. (Date.) getTime) ; +(. (Date.) getTime) ; <a timestamp> (.getTime (Date.)) ; exactly the same thing. ; Use / to call static methods -(System/currentTimeMillis) ; (system is always present) +(System/currentTimeMillis) ; <a timestamp> (system is always present) ; Use doto to make dealing with (mutable) classes more tolerable (import java.util.Calendar) diff --git a/examples/prism-concurnas.html b/examples/prism-concurnas.html index 82698f4416..87b9a4ce06 100644 --- a/examples/prism-concurnas.html +++ b/examples/prism-concurnas.html @@ -65,10 +65,10 @@

Full example

thesum = sum(1, 2, 3, 4, 5) //partially defined typedef -typedef NameMap = java.util.ArrayList>> +typedef NameMap<X> = java.util.ArrayList<java.util.HashMap<String, java.util.HashSet<X>>> //using typedefs... -nm NameMap= new NameMap() +nm NameMap<String>= new NameMap<String>() @Annotation class MyClass(a int, b int, c String){ @@ -79,11 +79,11 @@

Full example

mc2 = mc1@ //copy mc1 assert mc1 == mc2//same values! -assert mc1 &<> mc2//different objects! +assert mc1 &<> mc2//different objects! mc3 = mc1@(a = 100)//copy mc1 but overwrite value of a assert 'MyClass(100, 14, "hi there")' == mc3.toString() -mc4 = mc1@()//copy mc1 but exclude a and b +mc4 = mc1@(<a, b>)//copy mc1 but exclude a and b assert 'MyClass(0, 0, "hi there")' == mc3.toString() diff --git a/examples/prism-docker.html b/examples/prism-docker.html index 86511b3889..325d65035c 100644 --- a/examples/prism-docker.html +++ b/examples/prism-docker.html @@ -10,7 +10,7 @@

Full example

# VERSION 0.0.1 FROM ubuntu -MAINTAINER Victor Vieux +MAINTAINER Victor Vieux <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 diff --git a/examples/prism-factor.html b/examples/prism-factor.html index 6bde0a221f..90bb143025 100644 --- a/examples/prism-factor.html +++ b/examples/prism-factor.html @@ -55,8 +55,7 @@

Sequences

Regular Expressions

USE: regexp
-R/ abcde?.*+\?\.\*\+\/\\\/idmsr-idmsr/idmsr-idmsr
-
+R/ abcde?.*+\?\.\*\+\/\\\/idmsr-idmsr/idmsr-idmsr

Colon parsing words

: 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 @@

Comments

Control Flow

-Liquid provides multiple control flow statements. +

Liquid provides multiple control flow statements.

if


@@ -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.

Full Example query

diff --git a/examples/prism-tap.html b/examples/prism-tap.html index 4ed15fa754..d395678948 100644 --- a/examples/prism-tap.html +++ b/examples/prism-tap.html @@ -1,5 +1,5 @@

Full example

-
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

-
using Gtk;
+
using Gtk;
 
 int main (string[] args) {
 	Gtk.init(ref args);
@@ -30,4 +30,4 @@ 

Full example

Gtk.main(); return 0; -}
\ No newline at end of file +}
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, + 'A 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, / { + 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, a  element
+			if (node.tagName === 'pre') {
+				assert.equal(node.children.length, 1,
+					'
 element must have one and only one child node, a  element.'
+					+ ' 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 a  element.');
+				}
+				assert.equal(child.tagName, 'code', 'The child of a 
 element must be a  element.');
+			}
+
+			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);
+	});
+}