From 732de02d42e2368b95fe04f3c9bde653f6408282 Mon Sep 17 00:00:00 2001 From: Masatake YAMATO Date: Sun, 10 Jul 2022 03:23:37 +0900 Subject: [PATCH] JavaScript: extract variables and constants in destructuring assignment Close #1112. Signed-off-by: Masatake YAMATO --- .../js-destructural-binding.d/args.ctags | 1 + .../js-destructural-binding.d/expected.tags | 59 ++++++++ .../js-destructural-binding.d/input.js | 56 ++++++++ .../js-destructural-binding.d/validator | 1 + .../js-empty-class-name.d/expected.tags | 1 + parsers/jscript.c | 129 +++++++++++++++++- 6 files changed, 245 insertions(+), 2 deletions(-) create mode 100644 Units/parser-javascript.r/js-destructural-binding.d/args.ctags create mode 100644 Units/parser-javascript.r/js-destructural-binding.d/expected.tags create mode 100644 Units/parser-javascript.r/js-destructural-binding.d/input.js create mode 100644 Units/parser-javascript.r/js-destructural-binding.d/validator diff --git a/Units/parser-javascript.r/js-destructural-binding.d/args.ctags b/Units/parser-javascript.r/js-destructural-binding.d/args.ctags new file mode 100644 index 0000000000..5ee5f79f70 --- /dev/null +++ b/Units/parser-javascript.r/js-destructural-binding.d/args.ctags @@ -0,0 +1 @@ +--sort=no diff --git a/Units/parser-javascript.r/js-destructural-binding.d/expected.tags b/Units/parser-javascript.r/js-destructural-binding.d/expected.tags new file mode 100644 index 0000000000..b77bd2e284 --- /dev/null +++ b/Units/parser-javascript.r/js-destructural-binding.d/expected.tags @@ -0,0 +1,59 @@ +x input.js /^const [x] = [1];$/;" C +y input.js /^const [y, z] = [1, 2, 3, 4, 5];$/;" C +z input.js /^const [y, z] = [1, 2, 3, 4, 5];$/;" C +a input.js /^let [a=5, b=7] = [1];$/;" v +b input.js /^let [a=5, b=7] = [1];$/;" v +c input.js /^let [c, , d] = [1, 2, 3];$/;" v +d input.js /^let [c, , d] = [1, 2, 3];$/;" v +e input.js /^let [e, f = 0, , g] = [1, 2, 3, 4];$/;" v +f input.js /^let [e, f = 0, , g] = [1, 2, 3, 4];$/;" v +g input.js /^let [e, f = 0, , g] = [1, 2, 3, 4];$/;" v +h input.js /^const [h, i, ...[j, k]] = [1, 2, 3, 4];$/;" C +i input.js /^const [h, i, ...[j, k]] = [1, 2, 3, 4];$/;" C +j input.js /^const [h, i, ...[j, k]] = [1, 2, 3, 4];$/;" C +k input.js /^const [h, i, ...[j, k]] = [1, 2, 3, 4];$/;" C +l input.js /^const [l, m, ...[n, o, ...[p, q]]] = [1, 2, 3, 4, 5, 6];$/;" C +m input.js /^const [l, m, ...[n, o, ...[p, q]]] = [1, 2, 3, 4, 5, 6];$/;" C +n input.js /^const [l, m, ...[n, o, ...[p, q]]] = [1, 2, 3, 4, 5, 6];$/;" C +o input.js /^const [l, m, ...[n, o, ...[p, q]]] = [1, 2, 3, 4, 5, 6];$/;" C +p input.js /^const [l, m, ...[n, o, ...[p, q]]] = [1, 2, 3, 4, 5, 6];$/;" C +q input.js /^const [l, m, ...[n, o, ...[p, q]]] = [1, 2, 3, 4, 5, 6];$/;" C +A input.js /^const [A, B, ...{ C, D }] = []$/;" C +B input.js /^const [A, B, ...{ C, D }] = []$/;" C +C input.js /^const [A, B, ...{ C, D }] = []$/;" C +D input.js /^const [A, B, ...{ C, D }] = []$/;" C +E input.js /^ E: 42,$/;" p variable:user +F input.js /^ F: true$/;" p variable:user +user input.js /^const user = {$/;" v +E input.js /^const {E, F} = user;$/;" C +F input.js /^const {E, F} = user;$/;" C +G input.js /^const {E: G, F: H} = user;$/;" C +H input.js /^const {E: G, F: H} = user;$/;" C +I input.js /^const {I = 10, J = 5} = {I: 3};$/;" C +J input.js /^const {I = 10, J = 5} = {I: 3};$/;" C +I input.js /^const {I = 10, J = 5} = {I: 3};$/;" p variable:anonymousObject785a93f40105 +anonymousObject785a93f40105 input.js /^const {I = 10, J = 5} = {I: 3};$/;" v +K input.js /^let {a: K = 10, b: L = 5} = {a: 3};$/;" v +L input.js /^let {a: K = 10, b: L = 5} = {a: 3};$/;" v +a input.js /^let {a: K = 10, b: L = 5} = {a: 3};$/;" p variable:anonymousObject785a93f40205 +anonymousObject785a93f40205 input.js /^let {a: K = 10, b: L = 5} = {a: 3};$/;" v +M input.js /^let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40}$/;" v +N input.js /^let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40}$/;" v +O input.js /^let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40}$/;" v +M input.js /^let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40}$/;" p variable:anonymousObject785a93f40305 +N input.js /^let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40}$/;" p variable:anonymousObject785a93f40305 +c input.js /^let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40}$/;" p variable:anonymousObject785a93f40305 +d input.js /^let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40}$/;" p variable:anonymousObject785a93f40305 +anonymousObject785a93f40305 input.js /^let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40}$/;" v +title input.js /^ title: 'Scratchpad',$/;" p variable:metadata +anonymousObject785a93f40405 input.js /^ {$/;" v variable:metadata +locale input.js /^ locale: 'de',$/;" p variable:metadata.anonymousObject785a93f40405 +localization_tags input.js /^ localization_tags: [],$/;" p variable:metadata.anonymousObject785a93f40405 +last_edit input.js /^ last_edit: '2014-04-14T08:43:37',$/;" p variable:metadata.anonymousObject785a93f40405 +url input.js /^ url: '\/de\/docs\/Tools\/Scratchpad',$/;" p variable:metadata.anonymousObject785a93f40405 +title input.js /^ title: 'JavaScript-Umgebung'$/;" p variable:metadata.anonymousObject785a93f40405 +translations input.js /^ translations: [$/;" p variable:metadata +url input.js /^ url: '\/en-US\/docs\/Tools\/Scratchpad'$/;" p variable:metadata +metadata input.js /^const metadata = {$/;" v +englishTitle input.js /^ title: englishTitle, \/\/ rename$/;" v +localeTitle input.js /^ title: localeTitle, \/\/ rename$/;" v diff --git a/Units/parser-javascript.r/js-destructural-binding.d/input.js b/Units/parser-javascript.r/js-destructural-binding.d/input.js new file mode 100644 index 0000000000..5fc18b740f --- /dev/null +++ b/Units/parser-javascript.r/js-destructural-binding.d/input.js @@ -0,0 +1,56 @@ +// Derrived from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment +const [x] = [1]; +const [y, z] = [1, 2, 3, 4, 5]; +let [a=5, b=7] = [1]; + +let [c, , d] = [1, 2, 3]; +let [e, f = 0, , g] = [1, 2, 3, 4]; + +let [,,] = [1, 2, 3]; +let [,] = [1, 2, 3]; +let [] = [1, 2, 3]; + +const [h, i, ...[j, k]] = [1, 2, 3, 4]; +const [l, m, ...[n, o, ...[p, q]]] = [1, 2, 3, 4, 5, 6]; + +const [A, B, ...{ C, D }] = [] + +const user = { + E: 42, + F: true +}; + +const {E, F} = user; + +const {E: G, F: H} = user; + + +const {I = 10, J = 5} = {I: 3}; + +let {a: K = 10, b: L = 5} = {a: 3}; + +let {M, N, ...O} = {M: 10, N: 20, c: 30, d: 40} + + +const metadata = { + title: 'Scratchpad', + translations: [ + { + locale: 'de', + localization_tags: [], + last_edit: '2014-04-14T08:43:37', + url: '/de/docs/Tools/Scratchpad', + title: 'JavaScript-Umgebung' + } + ], + url: '/en-US/docs/Tools/Scratchpad' +}; + +let { + title: englishTitle, // rename + translations: [ + { + title: localeTitle, // rename + }, + ], +} = metadata; diff --git a/Units/parser-javascript.r/js-destructural-binding.d/validator b/Units/parser-javascript.r/js-destructural-binding.d/validator new file mode 100644 index 0000000000..64f5a0a681 --- /dev/null +++ b/Units/parser-javascript.r/js-destructural-binding.d/validator @@ -0,0 +1 @@ +node diff --git a/Units/parser-javascript.r/js-empty-class-name.d/expected.tags b/Units/parser-javascript.r/js-empty-class-name.d/expected.tags index 438218215f..3a51634fb7 100644 --- a/Units/parser-javascript.r/js-empty-class-name.d/expected.tags +++ b/Units/parser-javascript.r/js-empty-class-name.d/expected.tags @@ -1,2 +1,3 @@ +prop input.js /^var {prop} = { prop: "value" };$/;" v prop input.js /^var {prop} = { prop: "value" };$/;" p variable:anonymousObject4ca5b60a0105 anonymousObject4ca5b60a0105 input.js /^var {prop} = { prop: "value" };$/;" v diff --git a/parsers/jscript.c b/parsers/jscript.c index 4b55e90929..1ef6d254bb 100644 --- a/parsers/jscript.c +++ b/parsers/jscript.c @@ -2252,6 +2252,121 @@ static bool parseES6Class (tokenInfo *const token, const tokenInfo *targetName) return true; } +static void parseObjectDestructuring (tokenInfo *const token, bool is_const); +static void parseArrayDestructuring (tokenInfo *const token, bool is_const) +{ + int nest_level = 1; + bool in_left_side = true; + + while (nest_level > 0 && ! isType (token, TOKEN_EOF)) + { + readToken (token); + if (isType (token, TOKEN_OPEN_SQUARE)) + { + in_left_side = true; + nest_level++; + } + else if (isType (token, TOKEN_CLOSE_SQUARE)) + { + in_left_side = false; + nest_level--; + } + else if (isType (token, TOKEN_OPEN_CURLY)) + { + in_left_side = false; + parseObjectDestructuring (token, is_const); + } + else if (isType (token, TOKEN_COMMA) + || isType (token, TOKEN_DOTS)) + in_left_side = true; + else if (in_left_side && isType (token, TOKEN_IDENTIFIER)) + { + in_left_side = false; + makeJsTag (token, is_const ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); + } + else if (isType (token, TOKEN_EQUAL_SIGN)) + { + in_left_side = false; + /* TODO: SKIP */ + } + else + in_left_side = false; + } +} + +static void parseObjectDestructuring (tokenInfo *const token, bool is_const) +{ + tokenInfo *const name = newToken (); + + /* + * let { k0: v0, k1: v1 = 0, v3 }; + * | | || | | | + * ^...|..|^...|..|....^.....: start + * ....^..|....^..|..........: colon + * .......^.......^..........: emitted (made a tag for an id after colon) + */ + enum objDestructuringState { + OBJ_DESTRUCTURING_START, + OBJ_DESTRUCTURING_COLON, + OBJ_DESTRUCTURING_EMITTED, + } state = OBJ_DESTRUCTURING_START; + + while (! isType (token, TOKEN_EOF)) + { + readToken (token); + if (isType (token, TOKEN_OPEN_CURLY)) + { + parseObjectDestructuring (token, is_const); + if (state == OBJ_DESTRUCTURING_COLON) + state = OBJ_DESTRUCTURING_EMITTED; + } + else if (isType (token, TOKEN_CLOSE_CURLY)) + { + if (!vStringIsEmpty(name->string)) + makeJsTag (name, is_const ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); + break; + } + else if (isType (token, TOKEN_OPEN_SQUARE)) + { + parseArrayDestructuring (token, is_const); + if (state == OBJ_DESTRUCTURING_COLON) + state = OBJ_DESTRUCTURING_EMITTED; + } + else if (isType (token, TOKEN_IDENTIFIER)) + { + if (state == OBJ_DESTRUCTURING_COLON) + { + makeJsTag (token, is_const ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); + state = OBJ_DESTRUCTURING_EMITTED; + } + else if (state == OBJ_DESTRUCTURING_START + && vStringIsEmpty(name->string)) + copyToken(name, token, true); + } + else if (isType (token, TOKEN_COMMA)) + { + if (!vStringIsEmpty(name->string)) + { + makeJsTag (name, is_const ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); + vStringClear (name->string); + } + state = OBJ_DESTRUCTURING_START; + } + else if (isType (token, TOKEN_COLON)) + { + vStringClear (name->string); + state = OBJ_DESTRUCTURING_COLON; + } + else + { + if (state == OBJ_DESTRUCTURING_COLON) + state = OBJ_DESTRUCTURING_EMITTED; + } + } + + deleteToken (name); +} + static bool parseStatement (tokenInfo *const token, bool is_inside_class) { TRACE_ENTER_TEXT("is_inside_class: %s", is_inside_class? "yes": "no"); @@ -2322,6 +2437,14 @@ static bool parseStatement (tokenInfo *const token, bool is_inside_class) is_global = true; } readToken(token); + + if (is_global) + { + if (isType (token, TOKEN_OPEN_CURLY)) + parseObjectDestructuring (token, is_const); + else if (isType (token, TOKEN_OPEN_SQUARE)) + parseArrayDestructuring (token, is_const); + } } nextVar: @@ -2530,7 +2653,8 @@ static bool parseStatement (tokenInfo *const token, bool is_inside_class) * Handles this syntax: * var g_var2; */ - indexForName = makeJsTag (name, is_const ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); + if (! vStringIsEmpty (name->string)) + indexForName = makeJsTag (name, is_const ? JSTAG_CONSTANT : JSTAG_VARIABLE, NULL, NULL); } /* * Statement has ended. @@ -2749,7 +2873,8 @@ static bool parseStatement (tokenInfo *const token, bool is_inside_class) } } else if (! isType (token, TOKEN_KEYWORD) && - token->nestLevel == 0 && is_global ) + token->nestLevel == 0 && is_global && + (! vStringIsEmpty (name->string))) { /* * Only create variables for global scope