From 2bd10989533015cdbd4acb30a684d8b6dd0c45db Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Tue, 21 Apr 2020 17:44:41 -0400 Subject: [PATCH 1/4] fix(js/ts): 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. --- src/languages/javascript.js | 18 ++++++++++++++++-- src/languages/typescript.js | 19 ++++++++++++++++--- .../javascript/arrow-function.expect.txt | 11 ++++++++++- test/markup/javascript/arrow-function.txt | 10 ++++++++++ test/markup/javascript/jsx.expect.txt | 4 ++-- test/markup/typescript/functions.expect.txt | 9 +++++++++ test/markup/typescript/functions.txt | 10 ++++++++++ 7 files changed, 73 insertions(+), 8 deletions(-) diff --git a/src/languages/javascript.js b/src/languages/javascript.js index 9636f508da..1047daaa60 100644 --- a/src/languages/javascript.js +++ b/src/languages/javascript.js @@ -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", hljs.C_LINE_COMMENT_MODE,hljs.C_BLOCK_COMMENT_MODE] + }, hljs.C_BLOCK_COMMENT_MODE, hljs.C_LINE_COMMENT_MODE ]); @@ -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: /\)/, diff --git a/src/languages/typescript.js b/src/languages/typescript.js index 23d3fb34a4..010443615b 100644 --- a/src/languages/typescript.js +++ b/src/languages/typescript.js @@ -142,24 +142,37 @@ 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', + // eat recursive parens in sub expressions + { begin: /\(/, end: /\)/, + contains: ["self", hljs.C_LINE_COMMENT_MODE,hljs.C_BLOCK_COMMENT_MODE] + }, hljs.C_LINE_COMMENT_MODE, hljs.C_BLOCK_COMMENT_MODE ] diff --git a/test/markup/javascript/arrow-function.expect.txt b/test/markup/javascript/arrow-function.expect.txt index c7b648e5bc..2c0fcb17e1 100644 --- a/test/markup/javascript/arrow-function.expect.txt +++ b/test/markup/javascript/arrow-function.expect.txt @@ -1,4 +1,13 @@ var f = x => x; f(x => x + (y=2, z=undefined, ...rest) => y); -() => null; +() => 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 ); diff --git a/test/markup/javascript/arrow-function.txt b/test/markup/javascript/arrow-function.txt index 4e49e3405f..607c18eb51 100644 --- a/test/markup/javascript/arrow-function.txt +++ b/test/markup/javascript/arrow-function.txt @@ -2,3 +2,13 @@ var f = x => x; f(x => x + (y=2, z=undefined, ...rest) => y); () => null; const FC = props =>

functional component

; + +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 ); + diff --git a/test/markup/javascript/jsx.expect.txt b/test/markup/javascript/jsx.expect.txt index a000fe75a7..0f17aa4dd0 100644 --- a/test/markup/javascript/jsx.expect.txt +++ b/test/markup/javascript/jsx.expect.txt @@ -8,8 +8,8 @@ return (<node attr="value"></node>); -const n = () => <X /> -const m = () => <X x="" /> +const n = () => <X /> +const m = () => <X x="" /> class App extends Component { render() { diff --git a/test/markup/typescript/functions.expect.txt b/test/markup/typescript/functions.expect.txt index 3addcb3036..e2c98fc101 100644 --- a/test/markup/typescript/functions.expect.txt +++ b/test/markup/typescript/functions.expect.txt @@ -13,3 +13,12 @@ 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], []); +sides.every((length,width=(3+2+(4/5))) => length > 0 ); diff --git a/test/markup/typescript/functions.txt b/test/markup/typescript/functions.txt index 4985bdbfb9..2154438a8e 100644 --- a/test/markup/typescript/functions.txt +++ b/test/markup/typescript/functions.txt @@ -13,3 +13,13 @@ 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((acc, next) => [...acc, next], []); +sides.every((length,width=(3+2+(4/5))) => length > 0 ); + From 584d7b263cefc23562a9ab1ba696deb487c6e562 Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Tue, 21 Apr 2020 18:19:09 -0400 Subject: [PATCH 2/4] changelog --- CHANGES.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 4f9602069c..69a254632c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,13 @@ +## Version 10.1.0 (in progress) + +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][] + +[Josh Goebel]: https://github.com/yyyc514 + + ## Version 10.0.0 New languages: From 1e307bbd43b28b9b6b044fc9357f6611bd70326c Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Sun, 26 Apr 2020 13:01:38 -0400 Subject: [PATCH 3/4] allow much richer highlighting inside params --- src/languages/javascript.js | 2 +- test/markup/javascript/arrow-function.expect.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/languages/javascript.js b/src/languages/javascript.js index 1047daaa60..0ad9b3f18f 100644 --- a/src/languages/javascript.js +++ b/src/languages/javascript.js @@ -92,7 +92,7 @@ export default function(hljs) { var PARAMS_CONTAINS = SUBST.contains.concat([ // eat recursive parens in sub expressions { begin: /\(/, end: /\)/, - contains: ["self", hljs.C_LINE_COMMENT_MODE,hljs.C_BLOCK_COMMENT_MODE] + 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 diff --git a/test/markup/javascript/arrow-function.expect.txt b/test/markup/javascript/arrow-function.expect.txt index 2c0fcb17e1..9683eb3982 100644 --- a/test/markup/javascript/arrow-function.expect.txt +++ b/test/markup/javascript/arrow-function.expect.txt @@ -10,4 +10,4 @@ f(x => x 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 ); +sides.every((length,width=(3+2+(4/5))) => length > 0 ); From aa89ff227910d8a3269b51a3cb866b1c696df459 Mon Sep 17 00:00:00 2001 From: Josh Goebel Date: Sun, 26 Apr 2020 13:25:51 -0400 Subject: [PATCH 4/4] improve highlighting inside arguments on typescript --- src/languages/typescript.js | 66 +++++++++------------ test/markup/typescript/functions.expect.txt | 3 +- test/markup/typescript/functions.txt | 1 + 3 files changed, 30 insertions(+), 40 deletions(-) diff --git a/src/languages/typescript.js b/src/languages/typescript.js index 010443615b..9266b55e6b 100644 --- a/src/languages/typescript.js +++ b/src/languages/typescript.js @@ -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: [ @@ -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', @@ -168,14 +163,7 @@ export default function(hljs) { begin: /\(/, end: /\)/, excludeBegin: true, excludeEnd: true, keywords: KEYWORDS, - contains: [ - // eat recursive parens in sub expressions - { begin: /\(/, end: /\)/, - contains: ["self", hljs.C_LINE_COMMENT_MODE,hljs.C_BLOCK_COMMENT_MODE] - }, - hljs.C_LINE_COMMENT_MODE, - hljs.C_BLOCK_COMMENT_MODE - ] + contains: ARGUMENTS.contains } ] } @@ -222,7 +210,7 @@ export default function(hljs) { begin: '\\.' + hljs.IDENT_RE, relevance: 0 // hack: prevents detection of keywords after dots }, DECORATOR, - ARGS + ARGUMENTS ] }; } diff --git a/test/markup/typescript/functions.expect.txt b/test/markup/typescript/functions.expect.txt index e2c98fc101..d805a30c87 100644 --- a/test/markup/typescript/functions.expect.txt +++ b/test/markup/typescript/functions.expect.txt @@ -21,4 +21,5 @@ const bad = (() => 0); const bad = ((a, b) => [...a, b]); const array = [1, 2, 3].reduce<number[]>((acc, next) => [...acc, next], []); -sides.every((length,width=(3+2+(4/5))) => length > 0 ); +const bad = ((a=2, b=5) => [...a, b]); +sides.every((length,width=(3+2+(4/5))) => length > 0 ); diff --git a/test/markup/typescript/functions.txt b/test/markup/typescript/functions.txt index 2154438a8e..22d72f54ab 100644 --- a/test/markup/typescript/functions.txt +++ b/test/markup/typescript/functions.txt @@ -21,5 +21,6 @@ const bad = (_ => doSomething()); const bad = (() => 0); const bad = ((a, b) => [...a, b]); const array = [1, 2, 3].reduce((acc, next) => [...acc, next], []); +const bad = ((a=2, b=5) => [...a, b]); sides.every((length,width=(3+2+(4/5))) => length > 0 );