Skip to content

Commit

Permalink
fix(javascript/typescript): lambda with parens in parameters fails (#…
Browse files Browse the repository at this point in the history
…2502)

* fix(javascript/typescript): lambda with parens in parameters fails

- Fixes both JavaScript and TypeScript grammars

Fixes samples like:

    const bad = ((a, b) => [...a, b]);
    sides.every((length,width=(3+2+(4/5))) => length > 0 );

This is done by counting parens in the regex that finds arrows
functions. Currently we can only handle 2 levels of nesting as
shown in the second example above.

* allow much richer highlighting inside params
* improve highlighting inside arguments on typescript
  • Loading branch information
joshgoebel committed Apr 27, 2020
1 parent 7502e42 commit 0afd0d3
Show file tree
Hide file tree
Showing 8 changed files with 102 additions and 44 deletions.
5 changes: 4 additions & 1 deletion CHANGES.md
@@ -1,9 +1,12 @@
## Version 10.1.0 (in progress)

Language improvements:
Language Improvements:

- fix(javascript) `=>` function with nested `()` in params now works (#2502) [Josh Goebel][]
- fix(typescript) `=>` function with nested `()` in params now works (#2502) [Josh Goebel][]
- fix(yaml) Fix tags to include non-word characters (#2486) [Peter Plantinga][]

[Josh Goebel]: https://github.com/yyyc514
[Peter Plantinga]: https://github.com/pplantinga


Expand Down
18 changes: 16 additions & 2 deletions src/languages/javascript.js
Expand Up @@ -90,6 +90,10 @@ export default function(hljs) {
hljs.REGEXP_MODE
];
var PARAMS_CONTAINS = SUBST.contains.concat([
// eat recursive parens in sub expressions
{ begin: /\(/, end: /\)/,
contains: ["self"].concat(SUBST.contains, [hljs.C_BLOCK_COMMENT_MODE, hljs.C_LINE_COMMENT_MODE])
},
hljs.C_BLOCK_COMMENT_MODE,
hljs.C_LINE_COMMENT_MODE
]);
Expand Down Expand Up @@ -175,17 +179,27 @@ export default function(hljs) {
hljs.REGEXP_MODE,
{
className: 'function',
begin: '(\\(.*?\\)|' + IDENT_RE + ')\\s*=>', returnBegin: true,
// we have to count the parens to make sure we actually have the
// correct bounding ( ) before the =>. There could be any number of
// sub-expressions inside also surrounded by parens.
begin: '(\\([^(]*' +
'(\\([^(]*' +
'(\\([^(]*' +
'\\))?' +
'\\))?' +
'\\)|' + hljs.UNDERSCORE_IDENT_RE + ')\\s*=>', returnBegin: true,
end: '\\s*=>',
contains: [
{
className: 'params',
variants: [
{
begin: IDENT_RE
begin: hljs.UNDERSCORE_IDENT_RE
},
{
className: null,
begin: /\(\s*\)/,
skip: true
},
{
begin: /\(/, end: /\)/,
Expand Down
77 changes: 39 additions & 38 deletions src/languages/typescript.js
Expand Up @@ -27,38 +27,10 @@ export default function(hljs) {
'Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require ' +
'module console window document any number boolean string void Promise'
};

var DECORATOR = {
className: 'meta',
begin: '@' + JS_IDENT_RE,
};

var ARGS =
{
begin: '\\(',
end: /\)/,
keywords: KEYWORDS,
contains: [
'self',
hljs.QUOTE_STRING_MODE,
hljs.APOS_STRING_MODE,
hljs.NUMBER_MODE
]
};

var PARAMS = {
className: 'params',
begin: /\(/, end: /\)/,
excludeBegin: true,
excludeEnd: true,
keywords: KEYWORDS,
contains: [
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE,
DECORATOR,
ARGS
]
};
var NUMBER = {
className: 'number',
variants: [
Expand Down Expand Up @@ -113,8 +85,31 @@ export default function(hljs) {
NUMBER,
hljs.REGEXP_MODE
];


var ARGUMENTS =
{
begin: '\\(',
end: /\)/,
keywords: KEYWORDS,
contains: [
'self',
hljs.QUOTE_STRING_MODE,
hljs.APOS_STRING_MODE,
hljs.NUMBER_MODE
]
};
var PARAMS = {
className: 'params',
begin: /\(/, end: /\)/,
excludeBegin: true,
excludeEnd: true,
keywords: KEYWORDS,
contains: [
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE,
DECORATOR,
ARGUMENTS
]
};

return {
name: 'TypeScript',
Expand Down Expand Up @@ -142,27 +137,33 @@ export default function(hljs) {
hljs.REGEXP_MODE,
{
className: 'function',
begin: '(\\(.*?\\)|' + hljs.IDENT_RE + ')\\s*=>', returnBegin: true,
// we have to count the parens to make sure we actually have the
// correct bounding ( ) before the =>. There could be any number of
// sub-expressions inside also surrounded by parens.
begin: '(\\([^(]*' +
'(\\([^(]*' +
'(\\([^(]*' +
'\\))?' +
'\\))?' +
'\\)|' + hljs.UNDERSCORE_IDENT_RE + ')\\s*=>', returnBegin: true,
end: '\\s*=>',
contains: [
{
className: 'params',
variants: [
{
begin: hljs.IDENT_RE
begin: hljs.UNDERSCORE_IDENT_RE
},
{
className: null,
begin: /\(\s*\)/,
skip: true
},
{
begin: /\(/, end: /\)/,
excludeBegin: true, excludeEnd: true,
keywords: KEYWORDS,
contains: [
'self',
hljs.C_LINE_COMMENT_MODE,
hljs.C_BLOCK_COMMENT_MODE
]
contains: ARGUMENTS.contains
}
]
}
Expand Down Expand Up @@ -209,7 +210,7 @@ export default function(hljs) {
begin: '\\.' + hljs.IDENT_RE, relevance: 0 // hack: prevents detection of keywords after dots
},
DECORATOR,
ARGS
ARGUMENTS
]
};
}
11 changes: 10 additions & 1 deletion test/markup/javascript/arrow-function.expect.txt
@@ -1,4 +1,13 @@
<span class="hljs-keyword">var</span> f = <span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x;
f(<span class="hljs-function"><span class="hljs-params">x</span> =&gt;</span> x + <span class="hljs-function">(<span class="hljs-params">y=<span class="hljs-number">2</span>, z=<span class="hljs-literal">undefined</span>, ...rest</span>) =&gt;</span> y);
<span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="hljs-literal">null</span>;
<span class="hljs-function">() =&gt;</span> <span class="hljs-literal">null</span>;
<span class="hljs-keyword">const</span> FC = <span class="hljs-function"><span class="hljs-params">props</span> =&gt;</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">p</span>&gt;</span>functional component<span class="hljs-tag">&lt;/<span class="hljs-name">p</span>&gt;</span></span>;

<span class="hljs-keyword">const</span> good = <span class="hljs-function">() =&gt;</span> <span class="hljs-number">0</span>;
<span class="hljs-keyword">const</span> good = <span class="hljs-function">(<span class="hljs-params">x</span>) =&gt;</span> <span class="hljs-number">0</span>;
<span class="hljs-keyword">const</span> bad = (<span class="hljs-function"><span class="hljs-params">a</span> =&gt;</span> [...a, b]);
<span class="hljs-keyword">const</span> bad = (<span class="hljs-function"><span class="hljs-params">_</span> =&gt;</span> doSomething());
<span class="hljs-keyword">const</span> bad = (<span class="hljs-function">() =&gt;</span> <span class="hljs-number">0</span>);
<span class="hljs-keyword">const</span> bad = (<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> [...a, b]);
<span class="hljs-keyword">const</span> array = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>].reduce(<span class="hljs-function">(<span class="hljs-params">acc, next</span>) =&gt;</span> [...acc, next], []);
sides.every(<span class="hljs-function">(<span class="hljs-params">length,width=(<span class="hljs-number">3</span>+<span class="hljs-number">2</span>+(<span class="hljs-number">4</span>/<span class="hljs-number">5</span>))</span>) =&gt;</span> length &gt; <span class="hljs-number">0</span> );
10 changes: 10 additions & 0 deletions test/markup/javascript/arrow-function.txt
Expand Up @@ -2,3 +2,13 @@ var f = x => x;
f(x => x + (y=2, z=undefined, ...rest) => y);
() => null;
const FC = props => <p>functional component</p>;

const good = () => 0;
const good = (x) => 0;
const bad = (a => [...a, b]);
const bad = (_ => doSomething());
const bad = (() => 0);
const bad = ((a, b) => [...a, b]);
const array = [1, 2, 3].reduce((acc, next) => [...acc, next], []);
sides.every((length,width=(3+2+(4/5))) => length > 0 );

4 changes: 2 additions & 2 deletions test/markup/javascript/jsx.expect.txt
Expand Up @@ -8,8 +8,8 @@

<span class="hljs-keyword">return</span> (<span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">node</span> <span class="hljs-attr">attr</span>=<span class="hljs-string">"value"</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-name">node</span>&gt;</span></span>);

<span class="hljs-keyword">const</span> n = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">X</span> /&gt;</span></span>
<span class="hljs-keyword">const</span> m = <span class="hljs-function"><span class="hljs-params">()</span> =&gt;</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">X</span> <span class="hljs-attr">x</span>=<span class="hljs-string">""</span> /&gt;</span></span>
<span class="hljs-keyword">const</span> n = <span class="hljs-function">() =&gt;</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">X</span> /&gt;</span></span>
<span class="hljs-keyword">const</span> m = <span class="hljs-function">() =&gt;</span> <span class="xml"><span class="hljs-tag">&lt;<span class="hljs-name">X</span> <span class="hljs-attr">x</span>=<span class="hljs-string">""</span> /&gt;</span></span>

<span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">App</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">Component</span> </span>{
render() {
Expand Down
10 changes: 10 additions & 0 deletions test/markup/typescript/functions.expect.txt
Expand Up @@ -13,3 +13,13 @@
<span class="hljs-keyword">type</span> Foo = {
functionInFoo(): <span class="hljs-built_in">void</span>;
};

<span class="hljs-keyword">const</span> good = <span class="hljs-function">() =&gt;</span> <span class="hljs-number">0</span>;
<span class="hljs-keyword">const</span> good = <span class="hljs-function">(<span class="hljs-params">x</span>) =&gt;</span> <span class="hljs-number">0</span>;
<span class="hljs-keyword">const</span> bad = (<span class="hljs-function"><span class="hljs-params">a</span> =&gt;</span> [...a, b]);
<span class="hljs-keyword">const</span> bad = (<span class="hljs-function"><span class="hljs-params">_</span> =&gt;</span> doSomething());
<span class="hljs-keyword">const</span> bad = (<span class="hljs-function">() =&gt;</span> <span class="hljs-number">0</span>);
<span class="hljs-keyword">const</span> bad = (<span class="hljs-function">(<span class="hljs-params">a, b</span>) =&gt;</span> [...a, b]);
<span class="hljs-keyword">const</span> array = [<span class="hljs-number">1</span>, <span class="hljs-number">2</span>, <span class="hljs-number">3</span>].reduce&lt;<span class="hljs-built_in">number</span>[]&gt;(<span class="hljs-function">(<span class="hljs-params">acc, next</span>) =&gt;</span> [...acc, next], []);
<span class="hljs-keyword">const</span> bad = (<span class="hljs-function">(<span class="hljs-params">a=<span class="hljs-number">2</span>, b=<span class="hljs-number">5</span></span>) =&gt;</span> [...a, b]);
sides.every(<span class="hljs-function">(<span class="hljs-params">length,width=(<span class="hljs-params"><span class="hljs-number">3</span>+<span class="hljs-number">2</span>+(<span class="hljs-params"><span class="hljs-number">4</span>/<span class="hljs-number">5</span></span>)</span>)</span>) =&gt;</span> length &gt; <span class="hljs-number">0</span> );
11 changes: 11 additions & 0 deletions test/markup/typescript/functions.txt
Expand Up @@ -13,3 +13,14 @@ function getArray(): number[] {
type Foo = {
functionInFoo(): void;
};

const good = () => 0;
const good = (x) => 0;
const bad = (a => [...a, b]);
const bad = (_ => doSomething());
const bad = (() => 0);
const bad = ((a, b) => [...a, b]);
const array = [1, 2, 3].reduce<number[]>((acc, next) => [...acc, next], []);
const bad = ((a=2, b=5) => [...a, b]);
sides.every((length,width=(3+2+(4/5))) => length > 0 );

0 comments on commit 0afd0d3

Please sign in to comment.