diff --git a/.fantomasignore b/.fantomasignore
new file mode 100644
index 0000000..bc864c6
--- /dev/null
+++ b/.fantomasignore
@@ -0,0 +1 @@
+test/grammar.fsx
diff --git a/.gitignore b/.gitignore
index a41a0a7..a3259ac 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
node_modules
*.log
-/out
\ No newline at end of file
+out
+target
diff --git a/README.md b/README.md
index d788ac2..d1c0e3f 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,12 @@
-# Syntax highlighting for template strings in F#
+# Syntax highlighting for template strings in F\#
-VS Code extension that highlights HTML/SVG/CSS/SQL/JS F# triple quoted strings (interpolated or not) preceded by a function named `html/svg/css/sql/js`.
+VS Code extension that highlights embedded code in F# triple quoted strings (interpolated or not) preceded by a function named:
+
+- `html`
+- `svg`
+- `css`
+- `js`
+- `sql`
```fsharp
html """
Hello World!
"""
@@ -9,17 +15,54 @@ html """Hello World!
"""
You need to declare the functions by yourself. If you need to do some transformation, make the function accept `FormattableString`:
```fsharp
-let sql (s: FormattableString) = ...
+let sql (s: FormattableString) = // ...
```
-If you just want to trigger highlighting, use an identity function:
+> The extension will also accept prefixed function names (case-insensitive) like `toHTML`, `query_sql`, etc.
+
+If you just want to trigger highlighting, use a language comment (also case-insensitive) instead:
```fsharp
-let css (s: string) = s
+(* css *) """:root::before { content: "Hello World!"; }"""
```
-> The extension will also accept functions ending with `_html`/`_sql`/etc.
-
For HTML/CSS/JS templates, the extension will forward completion and hover requests to the built-in language providers in VS Code.
-![Example](screencast.gif)
\ No newline at end of file
+![Example](screencast.gif)
+
+## Contributing
+
+- Click **Run and Debug > Launch Extension**
+- Open [test cases](./test/grammar.fsx)
+
+### Grammar Debugging
+
+- Install [TextMate Languages](https://marketplace.visualstudio.com/items?itemName=pedro-w.tmlanguage) support
+- Update YAML and run **Commmand Palette > Convert to tmLanguage JSON**
+- Format YAML and JSON with [Prettier extension](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
+- Add recommended debug tokens to **User Settings**:
+
+ ```jsonc
+ "editor.tokenColorCustomizations": {
+ "textMateRules": [
+ {
+ "scope": [
+ "DEBUG"], "settings": {
+ "fontStyle": "underline",
+ "foreground": "#9A9A9A" }
+ },
+ {
+ "scope": [
+ "DEBUG.green"], "settings": {
+ // Inherits fontStyle from DEBUG
+ "foreground": "#17C984" }
+ },
+ // DEBUG.orange -> #FF8B2D
+ // DEBUG.red -> #C62B2B
+ ]
+ }
+ ```
+
+## Acknowledgements
+
+Inspired by [@mjbvz](https://github.com/mjbvz)'s JavaScript/TypeScript support for [Comment tagged templates](https://marketplace.visualstudio.com/items?itemName=bierner.comment-tagged-templates).
diff --git a/package.json b/package.json
index b05fc61..3f26d4c 100644
--- a/package.json
+++ b/package.json
@@ -4,24 +4,24 @@
"description": "Syntax highlighting for template strings in F#",
"icon": "fsharp.png",
"version": "1.4.1",
- "publisher": "alfonsogarciacaro",
+ "publisher": "alfonsogarciacaro",
"license": "MIT",
"repository": {
"url": "https://github.com/alfonsogarciacaro/vscode-template-fsharp-highlight.git"
},
"bugs": {
- "url": "https://github.com/alfonsogarciacaro/vscode-template-fsharp-highlight/issues"
+ "url": "https://github.com/alfonsogarciacaro/vscode-template-fsharp-highlight/issues"
+ },
+ "engines": {
+ "vscode": "^1.43.0"
},
- "engines": {
- "vscode": "^1.43.0"
- },
"categories": [
"Other"
],
- "activationEvents": [
- "onLanguage:fsharp",
+ "activationEvents": [
+ "onLanguage:fsharp",
"onCommand:template.fsharp.comment"
- ],
+ ],
"main": "./out/extension",
"contributes": {
"commands": [
@@ -35,57 +35,96 @@
}
],
"keybindings": [
- {
- "command": "template.fsharp.addComment",
- "key": "ctrl+shift+k ctrl+shift+c",
- "when": "editorFocus && editorLangId == 'fsharp'"
- },
- {
- "command": "template.fsharp.removeComment",
- "key": "ctrl+shift+k ctrl+shift+u",
- "when": "editorFocus && editorLangId == 'fsharp'"
- }
+ {
+ "command": "template.fsharp.addComment",
+ "key": "ctrl+shift+k ctrl+shift+c",
+ "when": "editorFocus && editorLangId == 'fsharp'"
+ },
+ {
+ "command": "template.fsharp.removeComment",
+ "key": "ctrl+shift+k ctrl+shift+u",
+ "when": "editorFocus && editorLangId == 'fsharp'"
+ }
],
"grammars": [
{
"injectTo": [
"source.fsharp"
],
- "scopeName": "template.fsharp.injection",
- "path": "./syntaxes/grammar.json",
+ "scopeName": "inline.template-fsharp-highlight",
+ "path": "./syntaxes/grammar.json"
+ },
+ {
+ "injectTo": [
+ "source.fsharp"
+ ],
+ "scopeName": "inline.template-fsharp-highlight.reinjection",
+ "path": "./syntaxes/reinject-grammar.json",
"embeddedLanguages": {
- "meta.embedded.string.html": "html",
- "meta.embedded.string.sql": "sql",
- "meta.embedded.string.css": "css",
- "meta.embedded.string.js": "js",
- "meta.embedded.interpolated.html": "html",
- "meta.embedded.interpolated.sql": "sql",
- "meta.embedded.interpolated.css": "css",
- "meta.embedded.interpolated.js": "js"
+ "meta.template.expression.fsharp": "fsharp"
}
},
{
"injectTo": [
"source.fsharp"
],
- "scopeName": "template.fsharp.reinjection",
- "path": "./syntaxes/reinject-grammar.json",
+ "scopeName": "inline.template-fsharp-highlight.injection.html",
+ "path": "./syntaxes/inject-html.json",
+ "embeddedLanguages": {
+ "string.quoted.triple.template.html.fsharp": "html"
+ }
+ },
+ {
+ "injectTo": [
+ "source.fsharp"
+ ],
+ "scopeName": "inline.template-fsharp-highlight.injection.svg",
+ "path": "./syntaxes/inject-svg.json",
"embeddedLanguages": {
- "meta.embedded.interpolated.substitution": "fsharp"
+ "string.quoted.triple.template.svg.fsharp": "xml"
+ }
+ },
+ {
+ "injectTo": [
+ "source.fsharp"
+ ],
+ "scopeName": "inline.template-fsharp-highlight.injection.css",
+ "path": "./syntaxes/inject-css.json",
+ "embeddedLanguages": {
+ "string.quoted.triple.template.css.fsharp": "css"
+ }
+ },
+ {
+ "injectTo": [
+ "source.fsharp"
+ ],
+ "scopeName": "inline.template-fsharp-highlight.injection.js",
+ "path": "./syntaxes/inject-js.json",
+ "embeddedLanguages": {
+ "string.quoted.triple.template.js.fsharp": "javascript"
+ }
+ },
+ {
+ "injectTo": [
+ "source.fsharp"
+ ],
+ "scopeName": "inline.template-fsharp-highlight.injection.sql",
+ "path": "./syntaxes/inject-sql.json",
+ "embeddedLanguages": {
+ "string.quoted.triple.template.sql.fsharp": "sql"
}
}
]
},
- "scripts": {
+ "scripts": {
"compile": "tsc -b",
- "watch": "tsc -b -w",
+ "watch": "tsc -b -w",
"pack": "vsce package"
- },
- "dependencies": {
- },
- "devDependencies": {
+ },
+ "dependencies": {},
+ "devDependencies": {
"@types/node": "^16.7.13",
- "@types/vscode": "^1.43.0",
+ "@types/vscode": "^1.43.0",
"typescript": "^4.2.2"
- }
-}
\ No newline at end of file
+ }
+}
diff --git a/syntaxes/grammar.json b/syntaxes/grammar.json
index e759013..223383e 100644
--- a/syntaxes/grammar.json
+++ b/syntaxes/grammar.json
@@ -1,143 +1,80 @@
{
- "scopeName": "template.fsharp.injection",
- "injectionSelector": "L:source.fsharp - (comment, string.quoted.double.fsharp, string.quoted.triple.fsharp)",
- "patterns": [
- {
- "begin": "[ ._](html|svg) *(?:(?= \\$?\"{3})|$)",
- "end": "(?<=\"{3})",
- "beginCaptures": {
- "1": {
- "name": "variable.fsharp"
- }
- },
- "patterns": [
- {
- "contentName": "meta.embedded.interpolated.html",
- "begin": "\\$\"{3}",
- "end": "\"{3}",
- "patterns": [
- {
- "include": "text.html.derivative"
- }
- ]
- },
- {
- "contentName": "meta.embedded.string.html",
- "begin": "\"{3}",
- "end": "\"{3}",
- "patterns": [
- {
- "include": "text.html.derivative"
- }
- ]
- },
-
- {
- "include": "source.fsharp"
- }
- ]
- },
- {
- "begin": "[ ._](sql) *(?:(?= \\$?\"{3})|$)",
- "end": "(?<=\"{3})",
- "beginCaptures": {
- "1": {
- "name": "variable.fsharp"
- }
- },
- "patterns": [
- {
- "contentName": "meta.embedded.interpolated.sql",
- "begin": "\\$\"{3}",
- "end": "\"{3}",
- "patterns": [
- {
- "include": "source.sql"
- }
- ]
- },
- {
- "contentName": "meta.embedded.string.sql",
- "begin": "\"{3}",
- "end": "\"{3}",
- "patterns": [
- {
- "include": "source.sql"
- }
- ]
- },
- {
- "include": "source.fsharp"
- }
- ]
- },
- {
- "begin": "[ ._](css) *(?:(?= \\$?\"{3})|$)",
- "end": "(?<=\"{3})",
- "beginCaptures": {
- "1": {
- "name": "variable.fsharp"
- }
- },
- "patterns": [
- {
- "contentName": "meta.embedded.interpolated.css",
- "begin": "\\$\"{3}",
- "end": "\"{3}",
- "patterns": [
- {
- "include": "source.css"
- }
- ]
- },
- {
- "contentName": "meta.embedded.string.css",
- "begin": "\"{3}",
- "end": "\"{3}",
- "patterns": [
- {
- "include": "source.css"
- }
- ]
- },
- {
- "include": "source.fsharp"
- }
- ]
- },
- {
- "begin": "[ ._](js) *(?:(?= \\$?\"{3})|$)",
- "end": "(?<=\"{3})",
- "beginCaptures": {
- "1": {
- "name": "variable.fsharp"
- }
- },
- "patterns": [
- {
- "contentName": "meta.embedded.interpolated.js",
- "begin": "\\$\"{3}",
- "end": "\"{3}",
- "patterns": [
- {
- "include": "source.js"
- }
- ]
- },
- {
- "contentName": "meta.embedded.string.js",
- "begin": "\"{3}",
- "end": "\"{3}",
- "patterns": [
- {
- "include": "source.js"
- }
- ]
- },
- {
- "include": "source.fsharp"
- }
- ]
- }
- ]
-}
\ No newline at end of file
+ "scopeName": "inline.template-fsharp-highlight",
+ "injectionSelector": "L:- (comment, string)",
+ "patterns": [
+ {
+ "include": "#lang_then_string"
+ },
+ {
+ "include": "#lang_then_newline"
+ }
+ ],
+ "repository": {
+ "lang_then_string": {
+ "contentName": "string.quoted.triple.template.${2:/downcase}.fsharp",
+ "begin": "(?xi) # Ignore whitespace in regex and case in matches\n(?<= # Start after valid lang ($2 above) with any prefix\n ( # Optionally capture:\n \\(\\* # - Opening comment\n \\ ? # - Single optional space\n )?\n (html|svg|css|js|sql)\n (?(1) # If captured opening comment then match:\n \\ ? # - Single optional space\n \\*\\) # - Closing comment\n | # Else skip match\n )\n)\n\\ * # Match potentially 0-length spaces\n(?=\\$?\"\"\") # End before triple quoted string\n",
+ "end": "$",
+ "patterns": [
+ {
+ "include": "#interpolated_string"
+ },
+ {
+ "include": "#triple_quoted_string"
+ }
+ ]
+ },
+ "lang_then_newline": {
+ "contentName": "string.quoted.triple.template.${1:/downcase}.fsharp",
+ "begin": "(?xi)\n(?<=\n (\n \\(\\*\n \\ ?\n )?\n (html|svg|css|js|sql)\n (?(1)\n \\ ?\n \\*\\)\n |\n )\n)\n\\ *\n$\n",
+ "applyEndPatternLast": true,
+ "end": "^",
+ "patterns": [
+ {
+ "include": "#newline_then_string"
+ }
+ ]
+ },
+ "newline_then_string": {
+ "begin": "(?x)\n^\n\\ *\n(?=\\$?\"\"\")\n",
+ "end": "$",
+ "patterns": [
+ {
+ "include": "#interpolated_string"
+ },
+ {
+ "include": "#triple_quoted_string"
+ }
+ ]
+ },
+ "triple_quoted_string": {
+ "contentName": "meta.embedded.line",
+ "begin": "\"\"\"",
+ "end": "\"\"\"",
+ "beginCaptures": {
+ "0": {
+ "name": "punctuation.definition.string.begin.fsharp"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.string.end.fsharp"
+ }
+ }
+ },
+ "interpolated_string": {
+ "contentName": "meta.embedded.block",
+ "begin": "\\$\"\"\"",
+ "end": "\"\"\"",
+ "beginCaptures": {
+ "0": {
+ "name": "punctuation.definition.string.begin.fsharp"
+ }
+ },
+ "endCaptures": {
+ "0": {
+ "name": "punctuation.definition.string.end.fsharp"
+ }
+ }
+ }
+ }
+}
diff --git a/syntaxes/grammar.yaml b/syntaxes/grammar.yaml
new file mode 100644
index 0000000..f9d771c
--- /dev/null
+++ b/syntaxes/grammar.yaml
@@ -0,0 +1,92 @@
+scopeName: inline.template-fsharp-highlight
+injectionSelector: "L:- (comment, string)"
+patterns:
+ - include: "#lang_then_string"
+ - include: "#lang_then_newline"
+repository:
+ lang_then_string:
+ # name: DEBUG
+ # Documented at https://macromates.com/blog/2011/format-strings/#the-case-for-replacements
+ contentName: string.quoted.triple.template.${2:/downcase}.fsharp
+ begin: |
+ (?xi) # Ignore whitespace in regex and case in matches
+ (?<= # Start after valid lang ($2 above) with any prefix
+ ( # Optionally capture:
+ \(\* # - Opening comment
+ \ ? # - Single optional space
+ )?
+ (html|svg|css|js|sql)
+ (?(1) # If captured opening comment then match:
+ \ ? # - Single optional space
+ \*\) # - Closing comment
+ | # Else skip match
+ )
+ )
+ \ * # Match potentially 0-length spaces
+ (?=\$?""") # End before triple quoted string
+ # Match content until line end
+ end: $
+ patterns:
+ - include: "#interpolated_string"
+ - include: "#triple_quoted_string"
+ lang_then_newline:
+ # Related to microsoft/vscode-textmate#114
+ # Same as above but split between 2 patterns
+ # name: DEBUG.red
+ contentName: string.quoted.triple.template.${1:/downcase}.fsharp
+ begin: |
+ (?xi)
+ (?<=
+ (
+ \(\*
+ \ ?
+ )?
+ (html|svg|css|js|sql)
+ (?(1)
+ \ ?
+ \*\)
+ |
+ )
+ )
+ \ *
+ $
+ # From end position of subpatterns
+ applyEndPatternLast: true
+ # Stop matching at start position
+ end: ^
+ patterns:
+ - include: "#newline_then_string"
+ newline_then_string:
+ # name: DEBUG.orange
+ # From line start
+ begin: |
+ (?x)
+ ^
+ \ *
+ (?=\$?""")
+ end: $
+ patterns:
+ - include: "#interpolated_string"
+ - include: "#triple_quoted_string"
+ triple_quoted_string:
+ # name: DEBUG.green
+ contentName: meta.embedded.line
+ begin: |-
+ """
+ end: |-
+ """
+ beginCaptures:
+ 0: { name: punctuation.definition.string.begin.fsharp }
+ endCaptures:
+ 0: { name: punctuation.definition.string.end.fsharp }
+ interpolated_string:
+ # Same as above but variant scope for interpolation
+ # name: DEBUG.green
+ contentName: meta.embedded.block
+ begin: \$"""
+ end: |-
+ """
+ beginCaptures:
+ 0: { name: punctuation.definition.string.begin.fsharp }
+ endCaptures:
+ 0: { name: punctuation.definition.string.end.fsharp }
diff --git a/syntaxes/inject-css.json b/syntaxes/inject-css.json
new file mode 100644
index 0000000..99776c2
--- /dev/null
+++ b/syntaxes/inject-css.json
@@ -0,0 +1,9 @@
+{
+ "scopeName": "inline.template-fsharp-highlight.injection.css",
+ "injectionSelector": "L:meta.embedded",
+ "patterns": [
+ {
+ "include": "source.css"
+ }
+ ]
+}
diff --git a/syntaxes/inject-css.yaml b/syntaxes/inject-css.yaml
new file mode 100644
index 0000000..33f9c8f
--- /dev/null
+++ b/syntaxes/inject-css.yaml
@@ -0,0 +1,4 @@
+scopeName: inline.template-fsharp-highlight.injection.css
+injectionSelector: "L:meta.embedded"
+patterns:
+ - include: source.css
diff --git a/syntaxes/inject-html.json b/syntaxes/inject-html.json
new file mode 100644
index 0000000..ead41ae
--- /dev/null
+++ b/syntaxes/inject-html.json
@@ -0,0 +1,9 @@
+{
+ "scopeName": "inline.template-fsharp-highlight.injection.html",
+ "injectionSelector": "L:meta.embedded",
+ "patterns": [
+ {
+ "include": "text.html.basic"
+ }
+ ]
+}
diff --git a/syntaxes/inject-html.yaml b/syntaxes/inject-html.yaml
new file mode 100644
index 0000000..c34a6f0
--- /dev/null
+++ b/syntaxes/inject-html.yaml
@@ -0,0 +1,4 @@
+scopeName: inline.template-fsharp-highlight.injection.html
+injectionSelector: "L:meta.embedded"
+patterns:
+ - include: text.html.basic
diff --git a/syntaxes/inject-js.json b/syntaxes/inject-js.json
new file mode 100644
index 0000000..e0ebc23
--- /dev/null
+++ b/syntaxes/inject-js.json
@@ -0,0 +1,9 @@
+{
+ "scopeName": "inline.template-fsharp-highlight.injection.js",
+ "injectionSelector": "L:meta.embedded",
+ "patterns": [
+ {
+ "include": "source.js"
+ }
+ ]
+}
diff --git a/syntaxes/inject-js.yaml b/syntaxes/inject-js.yaml
new file mode 100644
index 0000000..90d657c
--- /dev/null
+++ b/syntaxes/inject-js.yaml
@@ -0,0 +1,4 @@
+scopeName: inline.template-fsharp-highlight.injection.js
+injectionSelector: "L:meta.embedded"
+patterns:
+ - include: source.js
diff --git a/syntaxes/inject-sql.json b/syntaxes/inject-sql.json
new file mode 100644
index 0000000..c7d245c
--- /dev/null
+++ b/syntaxes/inject-sql.json
@@ -0,0 +1,9 @@
+{
+ "scopeName": "inline.template-fsharp-highlight.injection.sql",
+ "injectionSelector": "L:meta.embedded",
+ "patterns": [
+ {
+ "include": "source.sql"
+ }
+ ]
+}
diff --git a/syntaxes/inject-sql.yaml b/syntaxes/inject-sql.yaml
new file mode 100644
index 0000000..ecc28c7
--- /dev/null
+++ b/syntaxes/inject-sql.yaml
@@ -0,0 +1,4 @@
+scopeName: inline.template-fsharp-highlight.injection.sql
+injectionSelector: "L:meta.embedded"
+patterns:
+ - include: source.sql
diff --git a/syntaxes/inject-svg.json b/syntaxes/inject-svg.json
new file mode 100644
index 0000000..b6fd0dd
--- /dev/null
+++ b/syntaxes/inject-svg.json
@@ -0,0 +1,12 @@
+{
+ "scopeName": "inline.template-fsharp-highlight.injection.html",
+ "injectionSelector": "L:meta.embedded",
+ "patterns": [
+ {
+ "include": "text.svg"
+ },
+ {
+ "include": "text.xml"
+ }
+ ]
+}
diff --git a/syntaxes/inject-svg.yaml b/syntaxes/inject-svg.yaml
new file mode 100644
index 0000000..1ec6e40
--- /dev/null
+++ b/syntaxes/inject-svg.yaml
@@ -0,0 +1,7 @@
+scopeName: inline.template-fsharp-highlight.injection.html
+injectionSelector: "L:meta.embedded"
+patterns:
+ # If installed SVG extension then
+ - include: text.svg
+ # Else
+ - include: text.xml
diff --git a/syntaxes/reinject-grammar.json b/syntaxes/reinject-grammar.json
index 3a83f3d..fbd888e 100644
--- a/syntaxes/reinject-grammar.json
+++ b/syntaxes/reinject-grammar.json
@@ -1,16 +1,22 @@
{
- "scopeName": "template.fsharp.reinjection",
- "injectionSelector": "L:source.fsharp (meta.embedded.interpolated.html, meta.embedded.interpolated.sql, meta.embedded.interpolated.css, meta.embedded.interpolated.js) - meta.embedded.interpolated.substitution",
- "patterns": [
- {
- "contentName": "meta.embedded.interpolated.substitution",
- "begin": "(?Hello World!
+ """
+ svg """
+ Hello World!
+ """
+ css """
+ :root::before { content: "Hello World!"; }
+ """
+ js """
+ console.log("Hello World!");
+ """
+ sql """
+ SELECT "Hello World!";
+ """
+
+ let ``variant`` =
+ toHTML """Hello World!
"""
+ to_html """Hello World!
"""
+ toSVG """Hello World!"""
+ to_svg """Hello World!"""
+ toCSS """:root::before { content: "Hello World!"; }"""
+ to_css """:root::before { content: "Hello World!"; }"""
+ toJS """console.log("Hello World!");"""
+ to_js """console.log("Hello World!");"""
+ toSQL """SELECT "Hello World!";"""
+ to_sql """SELECT "Hello World!";"""
+
+ let ``followed by line break WITHOUT affecting subsequent lines`` =
+ html
+ """Hello World!
"""
+ ignore """Hello World!
"""
+ svg
+ """Hello World!"""
+ ignore """Hello World!"""
+ css
+ """:root::before { content: "Hello World!"; }"""
+ ignore """:root::before { content: "Hello World!"; }"""
+ js
+ """console.log("Hello World!");"""
+ ignore """console.log("Hello World!");"""
+ sql
+ """SELECT "Hello World!";"""
+ ignore """SELECT "Hello World!";"""
+
+ let ``when embedded languages are nested`` =
+ html """
+
+
+ Hello World!
+
+
+
+
+
+
+ """
+
+ endTestBindings
+
+ let ``comment`` =
+ ignore (* html *) """
+ Hello World!
+ """
+ ignore (* svg *) """
+ Hello World!
+ """
+ ignore (* css *) """
+ :root::before { content: "Hello World!"; }
+ """
+ ignore (* js *) """
+ console.log("Hello World!");
+ """
+ ignore (* sql *) """
+ SELECT "Hello World!";
+ """
+
+ let ``variant`` =
+ ignore (* HTML *) """Hello World!
"""
+ ignore (*html*) """Hello World!
"""
+ ignore (* SVG *) """Hello World!"""
+ ignore (*svg*) """Hello World!"""
+ ignore (* CSS *) """:root::before { content: "Hello World!"; }"""
+ ignore (*css*) """:root::before { content: "Hello World!"; }"""
+ ignore (* JS *) """console.log("Hello World!");"""
+ ignore (*js*) """console.log("Hello World!");"""
+ ignore (* SQL *) """SELECT "Hello World!";"""
+ ignore (*sql*) """SELECT "Hello World!";"""
+
+ let ``followed by line break WITHOUT affecting subsequent lines`` =
+ ignore (* html *)
+ """Hello World!
"""
+ ignore """Hello World!
"""
+ ignore (* svg *)
+ """Hello World!"""
+ ignore """Hello World!"""
+ ignore (* css *)
+ """:root::before { content: "Hello World!"; }"""
+ ignore """:root::before { content: "Hello World!"; }"""
+ ignore (* js *)
+ """console.log("Hello World!");"""
+ ignore """console.log("Hello World!");"""
+ ignore (* sql *)
+ """SELECT "Hello World!";"""
+ ignore """SELECT "Hello World!";"""
+
+ endTestBindings
+
+ endTestBindings
+
+let ``should NOT affect triple quoted string after language`` =
+
+ let ``function followed by additional tokens`` =
+ html <| """Hello World!
"""
+ svg <| """Hello World!"""
+ css <| """:root::before { content: "Hello World!"; }"""
+ js <| """console.log("Hello World!");"""
+ sql <| """SELECT "Hello World!";"""
+
+ let ``then line break`` =
+ html <|
+ """Hello World!
"""
+ svg <|
+ """Hello World!"""
+ css <|
+ """:root::before { content: "Hello World!"; }"""
+ js <|
+ """console.log("Hello World!");"""
+ sql <|
+ """SELECT "Hello World!";"""
+
+ endTestBindings
+
+ let ``comment followed by additional tokens`` =
+ ignore (* html *) <| """Hello World!
"""
+ ignore (* svg *) <| """Hello World!"""
+ ignore (* css *) <| """:root::before { content: "Hello World!"; }"""
+ ignore (* js *) <| """console.log("Hello World!");"""
+ ignore (* sql *) <| """SELECT "Hello World!";"""
+
+ let ``then line break`` =
+ ignore (* html *) <|
+ """Hello World!
"""
+ ignore (* svg *) <|
+ """Hello World!"""
+ ignore (* css *) <|
+ """:root::before { content: "Hello World!"; }"""
+ ignore (* js *) <|
+ """console.log("Hello World!");"""
+ ignore (* sql *) <|
+ """SELECT "Hello World!";"""
+
+ endTestBindings
+
+ let ``function in line comment`` =
+ // html """Hello World!
"""
+ // svg """Hello World!"""
+ // css """:root::before { content: "Hello World!"; }"""
+ // js """console.log("Hello World!");"""
+ // sql """SELECT "Hello World!";"""
+
+ let ``followed by line break`` =
+ // html
+ // """Hello World!
"""
+ // svg
+ // """Hello World!"""
+ // css
+ // """:root::before { content: "Hello World!"; }"""
+ // js
+ // """console.log("Hello World!");"""
+ // sql
+ // """SELECT "Hello World!";"""
+ endTestBindings
+
+ endTestBindings
+
+ let ``comment in line comment`` =
+ // ignore (* html *) """Hello World!
"""
+ // ignore (* svg *) """Hello World!"""
+ // ignore (* css *) """:root::before { content: "Hello World!"; }"""
+ // ignore (* js *) """console.log("Hello World!");"""
+ // ignore (* sql *) """SELECT "Hello World!";"""
+
+ let ``followed by line break`` =
+ // ignore (* html *)
+ // """Hello World!
"""
+ // ignore (* svg *)
+ // """Hello World!"""
+ // ignore (* css *)
+ // """:root::before { content: "Hello World!"; }"""
+ // ignore (* js *)
+ // """console.log("Hello World!");"""
+ // ignore (* sql *)
+ // """SELECT "Hello World!";"""
+ endTestBindings
+
+ endTestBindings
+
+ let ``function in block comment`` =
+ (*
+ html """Hello World!
"""
+ svg """Hello World!"""
+ css """:root::before { content: "Hello World!"; }"""
+ js """console.log("Hello World!");"""
+ sql """SELECT "Hello World!";"""
+ *)
+
+ let ``followed by line break`` =
+ (*
+ html
+ """Hello World!
"""
+ svg
+ """Hello World!"""
+ css
+ """:root::before { content: "Hello World!"; }"""
+ js
+ """console.log("Hello World!");"""
+ sql
+ """SELECT "Hello World!";"""
+ *)
+ endTestBindings
+
+ endTestBindings
+
+ let ``comment in line comment`` =
+ (*
+ ignore (* html *) """Hello World!
"""
+ ignore (* svg *) """Hello World!"""
+ ignore (* css *) """:root::before { content: "Hello World!"; }"""
+ ignore (* js *) """console.log("Hello World!");"""
+ ignore (* sql *) """SELECT "Hello World!";"""
+ *)
+
+ let ``followed by line break`` =
+ (*
+ ignore (* html *)
+ """Hello World!
"""
+ ignore (* svg *)
+ """Hello World!"""
+ ignore (* css *)
+ """:root::before { content: "Hello World!"; }"""
+ ignore (* js *)
+ """console.log("Hello World!");"""
+ ignore (* sql *)
+ """SELECT "Hello World!";"""
+ *)
+ endTestBindings
+
+ endTestBindings
+
+ endTestBindings
+
+let ``should highlight embedded code in interpolated string EXCEPT inside curly braces after language`` =
+ let greeting = "Hello World from F#!"
+
+ let ``function`` =
+ html $"""
+ {greeting}
+ """
+ svg $"""
+ {greeting}
+ """
+ css $"""
+ :root::before {{ content: "{greeting}"; }}
+ """
+ js $"""
+ console.log("{greeting}");
+ """
+ sql $"""
+ SELECT "{greeting};
+ """
+
+ let ``variant`` =
+ toHTML $"""{greeting}
"""
+ to_html $"""{greeting}
"""
+ toSVG $"""{greeting}"""
+ to_svg $"""{greeting}"""
+ toCSS $""":root::before {{ content: "{greeting}"; }}"""
+ to_css $""":root::before {{ content: "{greeting}"; }}"""
+ toJS $"""console.log("{greeting}");"""
+ to_js $"""console.log("{greeting}");"""
+ toSQL $"""SELECT "{greeting};"""
+ to_sql $"""SELECT "{greeting};"""
+
+ let ``followed by line break WITHOUT affecting subsequent lines`` =
+ html
+ $"""{greeting}
"""
+ ignore $"""{greeting}
"""
+ svg
+ $"""{greeting}"""
+ ignore $"""{greeting}"""
+ css
+ $""":root::before {{ content: "{greeting}"; }}"""
+ ignore $""":root::before {{ content: "{greeting}"; }}"""
+ js
+ $"""console.log("{greeting}");"""
+ ignore $"""console.log("{greeting}");"""
+ sql
+ $"""SELECT "{greeting}";"""
+ ignore $"""SELECT "{greeting}";"""
+
+ let ``when embedded languages are nested`` =
+ html $"""
+
+
+ {greeting}
+
+
+
+
+
+
+ """
+
+ endTestBindings
+
+ let ``comment`` =
+ ignore (* html *) $"""
+ {greeting}
+ """
+ ignore (* svg *) $"""
+ {greeting}
+ """
+ ignore (* css *) $"""
+ :root::before {{ content: "{greeting}"; }}
+ """
+ ignore (* js *) $"""
+ console.log("{greeting}");
+ """
+ ignore (* sql *) $"""
+ SELECT "{greeting};
+ """
+
+ let ``variant`` =
+ ignore (* HTML *) $"""{greeting}
"""
+ ignore (*html*) $"""{greeting}
"""
+ ignore (* SVG *) $"""{greeting}"""
+ ignore (*svg*) $"""{greeting}"""
+ ignore (* CSS *) $""":root::before {{ content: "{greeting}"; }}"""
+ ignore (*css*) $""":root::before {{ content: "{greeting}"; }}"""
+ ignore (* JS *) $"""console.log("{greeting}");"""
+ ignore (*js*) $"""console.log("{greeting}");"""
+ ignore (* SQL *) $"""SELECT "{greeting};"""
+ ignore (*sql*) $"""SELECT "{greeting};"""
+
+ let ``followed by line break WITHOUT affecting subsequent lines`` =
+ ignore (* html *)
+ $"""{greeting}
"""
+ ignore $"""{greeting}
"""
+ ignore (* svg *)
+ $"""{greeting}"""
+ ignore $"""{greeting}"""
+ ignore (* css *)
+ $""":root::before {{ content: "{greeting}"; }}"""
+ ignore $""":root::before {{ content: "{greeting}"; }}"""
+ ignore (* js *)
+ $"""console.log("{greeting}");"""
+ ignore $"""console.log("{greeting}");"""
+ ignore (* sql *)
+ $"""SELECT "{greeting}";"""
+ ignore $"""SELECT "{greeting}";"""
+
+ endTestBindings
+
+ endTestBindings
+
+let ``should NOT affect interpolated string after language`` =
+ let greeting = "Hello World from F#!"
+
+ let ``function followed by additional tokens`` =
+ html <| $"""{greeting}
"""
+ svg <| $"""{greeting}"""
+ css <| $""":root::before {{ content: "{greeting}"; }}"""
+ js <| $"""console.log("{greeting}");"""
+ sql <| $"""SELECT "{greeting}";"""
+
+ let ``then line break`` =
+ html <|
+ $"""{greeting}
"""
+ svg <|
+ $"""{greeting}"""
+ css <|
+ $""":root::before {{ content: "{greeting}"; }}"""
+ js <|
+ $"""console.log("{greeting}");"""
+ sql <|
+ $"""SELECT "{greeting}";"""
+
+ endTestBindings
+
+ let ``comment followed by additional tokens`` =
+ ignore (* html *) <| $"""{greeting}
"""
+ ignore (* svg *) <| $"""{greeting}"""
+ ignore (* css *) <| $""":root::before {{ content: "{greeting}"; }}"""
+ ignore (* js *) <| $"""console.log("{greeting}");"""
+ ignore (* sql *) <| $"""SELECT "{greeting}";"""
+
+ let ``then line break`` =
+ ignore (* html *) <|
+ $"""{greeting}
"""
+ ignore (* svg *) <|
+ $"""{greeting}"""
+ ignore (* css *) <|
+ $""":root::before {{ content: "{greeting}"; }}"""
+ ignore (* js *) <|
+ $"""console.log("{greeting}");"""
+ ignore (* sql *) <|
+ $"""SELECT "{greeting}";"""
+
+ endTestBindings
+
+ let ``function in line comment`` =
+ // html $"""{greeting}
"""
+ // svg $"""{greeting}"""
+ // css $""":root::before {{ content: "{greeting}"; }}"""
+ // js $"""console.log("{greeting}");"""
+ // sql $"""SELECT "{greeting}";"""
+
+ let ``followed by line break`` =
+ // html
+ // $"""{greeting}
"""
+ // svg
+ // $"""{greeting}"""
+ // css
+ // $""":root::before {{ content: "{greeting}"; }}"""
+ // js
+ // $"""console.log("{greeting}");"""
+ // sql
+ // $"""SELECT "{greeting}";"""
+ endTestBindings
+
+ endTestBindings
+
+ let ``comment in line comment`` =
+ // ignore (* html *) $"""{greeting}
"""
+ // ignore (* svg *) $"""{greeting}"""
+ // ignore (* css *) $""":root::before {{ content: "{greeting}"; }}"""
+ // ignore (* js *) $"""console.log("{greeting}");"""
+ // ignore (* sql *) $"""SELECT "{greeting}";"""
+
+ let ``followed by line break`` =
+ // ignore (* html *)
+ // $"""{greeting}
"""
+ // ignore (* svg *)
+ // $"""{greeting}"""
+ // ignore (* css *)
+ // $""":root::before {{ content: "{greeting}"; }}"""
+ // ignore (* js *)
+ // $"""console.log("{greeting}");"""
+ // ignore (* sql *)
+ // $"""SELECT "{greeting}";"""
+ endTestBindings
+
+ endTestBindings
+
+ let ``function in block comment`` =
+ (*
+ html $"""{greeting}
"""
+ svg $"""{greeting}"""
+ css $""":root::before {{ content: "{greeting}"; }}"""
+ js $"""console.log("{greeting}");"""
+ sql $"""SELECT "{greeting}";"""
+ *)
+
+ let ``followed by line break`` =
+ (*
+ html
+ $"""{greeting}
"""
+ svg
+ $"""{greeting}"""
+ css
+ $""":root::before {{ content: "{greeting}"; }}"""
+ js
+ $"""console.log("{greeting}");"""
+ sql
+ $"""SELECT "{greeting}";"""
+ *)
+ endTestBindings
+
+ endTestBindings
+
+ let ``comment in line comment`` =
+ (*
+ ignore (* html *) $"""{greeting}
"""
+ ignore (* svg *) $"""{greeting}"""
+ ignore (* css *) $""":root::before {{ content: "{greeting}"; }}"""
+ ignore (* js *) $"""console.log("{greeting}");"""
+ ignore (* sql *) $"""SELECT "{greeting}";"""
+ *)
+
+ let ``followed by line break`` =
+ (*
+ ignore (* html *)
+ $"""{greeting}
"""
+ ignore (* svg *)
+ $"""{greeting}"""
+ ignore (* css *)
+ $""":root::before {{ content: "{greeting}"; }}"""
+ ignore (* js *)
+ $"""console.log("{greeting}");"""
+ ignore (* sql *)
+ $"""SELECT "{greeting}";"""
+ *)
+ endTestBindings
+
+ endTestBindings
+
+ endTestBindings