Commit c1239bd6 authored by Jeffery To's avatar Jeffery To Committed by Christian Hergert

jsx.lang: Add language definition

parent 50e55021
<?xml version="1.0" encoding="UTF-8"?>
<!--
This file is part of GtkSourceView
Author: Jeffery To <jeffery.to@gmail.com>
Copyright (C) 2019 Jeffery To <jeffery.to@gmail.com>
GtkSourceView is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
GtkSourceView is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this library; if not, see <http://www.gnu.org/licenses/>.
-->
<language id="jsx" name="JSX" version="2.0" _section="Script">
<metadata>
<property name="mimetypes">application/jsx;application/x-jsx;text/x-jsx;text/jsx</property>
<property name="globs">*.jsx</property>
<property name="line-comment-start">//</property>
<property name="block-comment-start">/*</property>
<property name="block-comment-end">*/</property>
</metadata>
<styles>
<style id="element" name="Element"/>
<style id="spread-attribute" name="Spread attribute"/>
<style id="attribute-expression" name="Attribute expression"/>
<style id="child-expression" name="Child expression"/>
</styles>
<keyword-char-class>[a-zA-Z0-9_$-]</keyword-char-class>
<definitions>
<!-- Based on the JSX spec: https://github.com/facebook/jsx -->
<!-- using a style from another lang file before referencing a
context from the file causes "style not defined" errors
-->
<context id="style-not-defined-error-workaround">
<include>
<context ref="xml:xml"/>
</include>
</context> <!-- /style-not-defined-error-workaround -->
<!-- # General -->
<!-- reference js here to avoid "unknown id" error when referencing
a regex from js before referencing a context from js
-->
<context id="main-lang">
<include>
<context ref="js:js"/>
</include>
</context> <!-- /main-lang -->
<define-regex id="_start-tag-start">(?:&lt;(?!/))</define-regex>
<!-- # Names -->
<define-regex id="_identifier-part" extended="true">
(?: \%{js:identifier-part} | - )
</define-regex> <!-- /_identifier-part -->
<!-- <JSXIdentifier> -->
<define-regex id="_identifier" extended="true">
\%{js:identifier-start} \%{_identifier-part}*
</define-regex> <!-- /_identifier -->
<context id="_identifier" once-only="true">
<match>\%{_identifier}</match>
</context> <!-- /_identifier -->
<context id="_choice-identifier" end-parent="true">
<start>(?=\%{js:identifier-start})</start>
<end>\%{_identifier}</end>
</context> <!-- /_choice-identifier -->
<!-- <JSXNamespacedName> -->
<define-regex id="_namespaced-name" extended="true">
(?: \%{_identifier} : \%{_identifier} )
</define-regex> <!-- /_namespaced-name -->
<context id="_choice-namespaced-name" end-parent="true">
<start>(?=\%{_namespaced-name})</start>
<end>\%{_namespaced-name}</end>
</context> <!-- /_choice-namespaced-name -->
<!-- <JSXMemberExpression> -->
<define-regex id="_member-expression" extended="true">
(?: \%{_identifier} (?: \. \%{_identifier} )+ )
</define-regex> <!-- /_member-expression -->
<context id="_choice-member-expression" end-parent="true">
<start>(?=\%{_member-expression})</start>
<end>\%{_member-expression}</end>
</context> <!-- /_choice-member-expression -->
<!-- # Element name -->
<!-- <JSXElementName> -->
<context id="_element-name" style-ref="xml:element-name" once-only="true">
<start>(?=\%{js:identifier-start})</start>
<end>\%{js:before-next-token}</end>
<include>
<context ref="js:comments"/>
<context id="_element-name-content">
<include>
<context ref="_choice-namespaced-name"/>
<context ref="_choice-member-expression"/>
<context ref="_choice-identifier"/>
</include>
</context> <!-- /_element-name-content -->
</include>
</context> <!-- /_element-name -->
<context id="_ordered-element-name" once-only="true">
<start>\%{js:before-next-token}</start>
<end>\%{js:before-next-token}</end>
<include>
<context ref="_element-name"/>
</include>
</context> <!-- /_ordered-element-name -->
<!-- # Attributes -->
<!-- ## Spread attributes -->
<!-- <JSXSpreadAttribute> -->
<context id="_spread-attributes" style-ref="spread-attribute">
<start>{</start>
<end>}</end>
<include>
<context ref="js:comments"/>
<context id="_spread-attribute-content">
<include>
<context ref="js:ordered-spread-syntax"/>
<context ref="js-expr:expression-without-comma"/>
</include>
</context> <!-- /_spread-attribute-content -->
</include>
</context> <!-- /_spread-attributes -->
<!-- ## Attribute name -->
<!-- <JSXAttributeName> -->
<context id="_attribute-names" style-ref="xml:attribute-name">
<start>(?=\%{js:identifier-start})</start>
<end>\%{js:before-next-token}</end>
<include>
<context ref="js:comments"/>
<context id="_attribute-name-content">
<include>
<context ref="_choice-namespaced-name"/>
<context ref="_choice-identifier"/>
</include>
</context> <!-- /_attribute-name-content -->
</include>
</context> <!-- /_attribute-names -->
<!-- ## Attribute value -->
<!-- <JSXAttributeValue> (part of) -->
<context id="_choice-attribute-value-string" style-ref="xml:attribute-value" end-parent="true" class="string" class-disabled="no-spell-check">
<start>["']</start>
<end>\%{0@start}</end>
<include>
<!-- no comments here -->
<context id="_attribute-value-string-content">
<include>
<!-- javascript escapes do not appear to be parsed here
but xml entities / character references appear to be
parsed -->
<context ref="xml:entity"/>
<context ref="xml:character-reference"/>
</include>
</context> <!-- /_attribute-value-string-content -->
</include>
</context> <!-- /_choice-attribute-value-string -->
<!-- <JSXAttributeValue> (part of) -->
<context id="_choice-attribute-value-expression" style-ref="attribute-expression" end-parent="true">
<start>{</start>
<end>}</end>
<include>
<context ref="js:comments"/>
<context id="_attribute-value-expression-content">
<include>
<!-- no spread syntax here -->
<context ref="js-expr:expression-without-comma"/>
</include>
</context> <!-- /_attribute-value-expression-content -->
</include>
</context> <!-- /_choice-attribute-value-expression -->
<!-- ## Attribute initializer -->
<!-- <JSXAttributeInitializer> -->
<context id="_attribute-initializers">
<start>=</start>
<end>\%{js:before-next-token}</end>
<include>
<context sub-pattern="0" where="start" style-ref="xml:attribute-name"/>
<context ref="js:comments"/>
<context id="_attribute-initializer-content">
<include>
<context ref="_choice-attribute-value-string"/>
<context ref="_choice-attribute-value-expression"/>
<context ref="choice-element"/>
</include>
</context> <!-- /_attribute-initializer-content -->
</include>
</context> <!-- /_attribute-initializers -->
<!-- # Element / fragment -->
<context id="_element-content">
<include>
<!-- ## Start tag -->
<!-- <JSXOpeningElement> / <JSXSelfClosingElement> -->
<context id="_start-tag-head" style-ref="xml:tag" once-only="true" class="no-spell-check">
<start>\%{_start-tag-start}</start>
<end>(?=/?&gt;)</end>
<include>
<context sub-pattern="0" where="start" style-ref="xml:element-name"/>
<context ref="js:comments"/>
<context id="_start-tag-head-content">
<include>
<context ref="_ordered-element-name"/>
<context ref="_spread-attributes"/>
<context ref="_attribute-names"/>
<context ref="_attribute-initializers"/>
</include>
</context> <!-- /_start-tag-head-content -->
</include>
</context> <!-- /_start-tag-head -->
<context id="_start-tag-tail-empty-element-end-parent" style-ref="xml:tag" end-parent="true" class="no-spell-check">
<start>(?=/&gt;)</start>
<end>/&gt;</end>
<include>
<context sub-pattern="0" where="end" style-ref="xml:element-name"/>
</include>
</context> <!-- /_start-tag-tail-empty-element-end-parent -->
<context id="_start-tag-tail" style-ref="xml:tag" once-only="true" class="no-spell-check">
<match>&gt;</match>
<include>
<context sub-pattern="0" style-ref="xml:element-name"/>
</include>
</context> <!-- /_start-tag-tail -->
<!-- ## End tag -->
<context id="_end-tag-end-parent" style-ref="xml:tag" end-parent="true" class="no-spell-check">
<start>&lt;/</start>
<end>&gt;</end>
<include>
<context sub-pattern="0" where="start" style-ref="xml:element-name"/>
<context sub-pattern="0" where="end" style-ref="xml:element-name"/>
<context ref="js:comments"/>
<context id="_end-tag-content">
<include>
<context ref="_ordered-element-name"/>
</include>
</context> <!-- /_end-tag-content -->
</include>
</context> <!-- /_end-tag-end-parent -->
<!-- ## Nested elements -->
<!-- <JSXElement> / <JSXFragment> -->
<context id="_elements" style-ref="element" class-disabled="no-spell-check">
<start>(?=\%{_start-tag-start})</start>
<include>
<!-- no comments here - comments appear to be parsed as regular
text -->
<context ref="_element-content"/>
</include>
</context> <!-- /_elements -->
<!-- ## Child expressions -->
<!-- <JSXChild> / <JSXChildExpression> -->
<context id="_child-expressions" style-ref="child-expression" class="no-spell-check">
<start>{</start>
<end>}</end>
<include>
<context ref="js:comments"/>
<context id="_child-expression-content">
<include>
<context ref="js:ordered-spread-syntax"/>
<context ref="js-expr:expression-without-comma"/>
</include>
</context> <!-- /_child-expression-content -->
</include>
</context> <!-- /_child-expressions -->
<!-- ## XML syntax -->
<!-- javascript escapes do not appear to be parsed here
but xml character entity / numeric character references
appear to be parsed -->
<context ref="xml:entity"/>
<context ref="xml:character-reference"/>
</include>
</context> <!-- /_element-content -->
<!-- <JSXElement> / <JSXFragment> -->
<context id="choice-element" style-ref="element" end-parent="true" class-disabled="no-spell-check">
<start>(?=\%{_start-tag-start})</start>
<include>
<!-- no comments here - comments appear to be parsed as regular
text -->
<context ref="_element-content"/>
</include>
</context> <!-- /choice-element -->
<!-- # Primary expression -->
<context id="_jsx-primary-expression-content">
<include>
<context ref="choice-element"/>
<context ref="js-expr:_primary-expression-content" original="true"/>
</include>
</context> <!-- /_jsx-primary-expression-content -->
<replace id="js-expr:_primary-expression-content" ref="_jsx-primary-expression-content"/>
<!-- # Main context -->
<context id="jsx" class="no-spell-check">
<include>
<context ref="main-lang"/>
</include>
</context> <!-- /jsx -->
</definitions>
</language>
......@@ -77,6 +77,7 @@ data/language-specs/javascript-statements.lang
data/language-specs/javascript-values.lang
data/language-specs/j.lang
data/language-specs/json.lang
data/language-specs/jsx.lang
data/language-specs/julia.lang
data/language-specs/kotlin.lang
data/language-specs/latex.lang
......
/*
* JSX Elements
*/
// Element name
( <div></div> );
( <my-custom-component></my-custom-component> );
( <namespace:component></namespace:component> );
( <Module.Sub.Component></Module.Sub.Component> );
// Attributes
( <div {...props}></div> ); // spread attributes
( <div class="main"></div> );
( <namespace:component namespace:attribute='value'></namespace:component> );
( <div class={classes[0]}></div> );
// Empty element
( <img /> );
// Nested elements
(
<div>
<span></span>
<img />
</div>
);
// Child expression
(
<div>
{["1", 2, three].join('+')}
{...obj}
</div>
);
// XML character entity / numeric character references
( <div>&gt;&#47;</div> );
// Fragment
(
<>
<div>
<img />
</div>
<div>
<span></span>
</div>
</>
);
// from file.js
/*
* Expressions (in expression statements)
*/
/*
* Literals
*/
/* Keyword values */
var NULL = null;
var TRUE = true;
var FALSE = false;
/* Number */
var decimal1 = 0;
var decimal2 = 123.45;
var decimal3 = .66667;
var decimal4 = 10e20;
var decimal5 = 0.2e+1;
var decimal6 = .5E-20;
var hex1 = 0xDEADBEEF;
var hex2 = 0Xcafebabe;
// ES2015 binary and octal numbers
let binary1 = 0b1010;
let binary2 = 0B00001111;
let octal1 = 0o0123;
let octal2 = 0O4567;
// Legacy octal numbers
var legacy_octal1 = 01;
var legacy_octal2 = 007;
// BigInt (ES2020)
var decimal1 = 0n;
var decimal2 = 123n;
var hex1 = 0xDEADBEEFn;
var hex2 = 0Xcafebaben;
var binary1 = 0b1010n;
var binary2 = 0B00001111n;
var octal1 = 0o0123n;
var octal2 = 0O4567n;
/* String */
// Escape sequences
'\b\f\n\r\t\v\0\'\"\\'; // Single character escape
"\1\01\001"; // Octal escape (Annex B)
'\xA9'; // Hexadecimal escape
"\u00a9"; // Unicode escape
'\u{1D306}'; // Unicode code point escape
/* Array literal */
[];
[1];
[1.0, 'two', 0x03];
// Trailing comma
[
[1,2,3],
[4,5,6],
];
// Spread syntax
[1, ...a, 2];
/* Object literal */
a = {};
a = { prop: 'value' };
a = { prop: 'value', extends: 1 };
// Trailing comma
a = {
prop: 'value',
extends: 1,
};
// Shorthand property names
a = { b, c, d };
// Getter / setter
a = {
_hidden: null,
get property() { return _hidden; },
set property(value) { this._hidden = value; }
};
// Shorthand function notation
a = {
method() {},
*generator() {},
// Async function (ES2017)
async method() {},
async /* comment */ method() {},
async() {},// method called "async"
async: false, // property called "async"
async prop: 'val', // incorrectly highlighted (syntax error)
// Async generator (ES2018)
async *generator() {}
};
// Computed property names
a = {
['prop']: 1,
['method']() {}
};
// Spread properties (ES2018)
a = { ...b };
/* Regular expression literal */
/abc/;
x = /abc/gi;
function_with_regex_arg(/abc/);
[ /abc/m, /def/u ];
a = { regex: /abc/s }; // s (dotAll): ES2018
(1 === 0) ? /abc/ : /def/;
/abc/; /* Comment */
/abc/; // Comment
var matches = /abc/.exec('Alphabet ... that should contain abc, right?');
// No regex here
a = [thing / thing, thing / thing];
x = a /b/ c / d;
// Character groups with backslashes
/[ab\\]/; // a, b or backslash
/[ab\]]/; // a, b or ]
/\\[ab]/; // a or b preceded by backslash
/\[ab]/; // Literally "[ab]"
// Control escape
/\cJ/;
// Unicode property escape (ES2018)
/\p{General_Category=Letter}/u;
/\p{Letter}/u;
// Named capture groups (ES2018)
/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
/(?<foobar>foo|bar)/u;
/^(?<half>.*).\k<half>$/u; // backreference
/* Template literal */
console.log(`The sum of 2 and 2 is ${2 + 2}`);
let y = 8;
let my_string = `This is a multiline
string that also contains
a template ${y + (4.1 - 2.2)}`;
/*
* Built-in values
*/
// global object values
Infinity;
NaN;
undefined;
// global object functions
decodeURIComponent();
decodeURI();
encodeURIComponent();
encodeURI();
eval();
isFinite();
isNaN();
parseFloat();
parseInt();
// constructors (subset)
Array();
BigInt(); // ES2020
Boolean();
Date();
Error();
Function();
Map();
Object();
Promise();
RegExp();
Set();
String();
Symbol();
// objects
JSON.parse();
Math.random();
// object keywords
arguments;
globalThis; // ES2020
new.target;
new . /* comment */ target;
super;
this;
new . /* comment
*/ target; // not correctly highlighted
new // comment
.target; // not correctly highlighted
// function keywords
import(); // ES2020
import /* comment */ (); // ES2020
import /* comment
*/ (); // not correctly highlighted (though it may appear correct)
import // comment
(); // not correctly highlighted (though it may appear correct)
// properties (subset)
array.length;
Math.PI;
Number.NaN;
object.constructor;
Class.prototype;
Symbol.asyncIterator; // ES2018
Symbol('desc').description; // ES2019
// methods (subset)
array.keys();
date.toString();
object.valueOf();
re.test();
array.includes(); // ES2016
Object.values(); // ES2017
Object.entries(); // ES2017
string.padStart(); // ES2017
string.padEnd(); // ES2017
Object.getOwnPropertyDescriptors(); // ES2017
promise.finally(); // ES2018
Object.fromEntries(); // ES2019
string.trimStart(); // ES2019
string.trimEnd(); // ES2019
array.flat(); // ES2019
array.flatMap(); // ES2019
string.matchAll(); // ES2020
Promise.allSettled(); // ES2020
BigInt.asUintN(); // ES2020
/*
* Function expression, arrow function
*/
a = function () { return 1 };
a = function fn() {
return;
};
a = function fn(x) {};
a = function fn(x, y) {};
// Arrow function
x => -x;
() => {};
(x, y) => x + y;
(x, y) => { return x + y; };
(x, y) => /* comment */ { return x + y; } /* comment */ ;
(x) => ({ a: x }); // return object
// Default parameters
a = function fn(x, y = 1) {};
(x, y = 1) => x + y;
// Parameter without default after default parameters
a = function fn(x = 1, y) {};
(x = 1, y) => x + y;
// Array destructuring
a = function fn([x]) {};
a = function fn([x, y]) {};
([x]) => x;
([x, y]) => x + y;
// Object destructuring
a = function fn({ x }) {};
a = function fn({ x, b: y }) {};
({ x }) => x;
({ x, b: y }) => x + y;