From 18d1f19dbc2100cd09d449b085159e17c2f2a225 Mon Sep 17 00:00:00 2001 From: Steven Nguyen Date: Sun, 2 Oct 2022 18:05:29 -0500 Subject: [PATCH] migrate https://github.com/atom/language-javascript/pull/686 The non-tree sitter version is almost completely copied so nothing to say there. There are some inconsistencies/missing operators in the tree-sitter version, for example: - `this` - `new.target` - `import.meta` - The keyword.operator order looks more wrong the more I look at it but that's for another day because of the possible scope regularity change in the future: https://github.com/atom/language-javascript/pull/690 - btw https://github.com/atom/language-javascript/pull/691 looks good to me I should probably do that Even though tree-sitter will have some above changes I added the new operators (`** **= ?? &&= ||= ??= void`) anyway. Well `void` is not new it's just missing. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators for a list of operators. Of course while the comma is technically an operator, hopefully it's used more to separate elements in arrays or properties in objects. Also `delete` is an operator not control. --- .../grammars/javascript.cson | 508 +++++++++++++----- .../grammars/tree-sitter-javascript.cson | 17 +- .../spec/javascript-spec.coffee | 94 +++- 3 files changed, 469 insertions(+), 150 deletions(-) diff --git a/packages/language-javascript/grammars/javascript.cson b/packages/language-javascript/grammars/javascript.cson index 62011cefab..dccbbc2508 100644 --- a/packages/language-javascript/grammars/javascript.cson +++ b/packages/language-javascript/grammars/javascript.cson @@ -1,3 +1,6 @@ +# Note: Because of the optional chaining operator (?.), "object.property" in the comments usually means ("object.property" or "object?.property") +# Even though sometimes it doesn't seem like using the chaining operator would make any sense, it's still supported. example: Math?.PI +# Negative look-behinds for "." should cover "?." 'scopeName': 'source.js' 'fileTypes': [ 'js' @@ -284,8 +287,8 @@ ] } { - # [.]foo = function... - 'begin': '(?=(\\.)?[a-zA-Z_$][\\w$]*\\s*=\\s*(\\basync\\b\\s*)?\\bfunction\\b)' + # [?.]foo = function... + 'begin': '(?=(\\?\\.|\\.)?[a-zA-Z_$][\\w$]*\\s*=\\s*(\\basync\\b\\s*)?\\bfunction\\b)' 'end': '(?<=})' 'patterns': [ { @@ -300,10 +303,14 @@ 'name': 'meta.function.js' 'patterns': [ { - 'match': '(\\.)?([a-zA-Z_$][\\w$]*)\\s*(=)\\s*' + 'match': '(\\?\\.|\\.)?([a-zA-Z_$][\\w$]*)\\s*(=)\\s*' 'captures': '1': - 'name': 'meta.delimiter.method.period.js' + 'patterns': [ + { + 'include': '#period_or_optional_method' + } + ] '2': 'name': 'entity.name.function.js' '3': @@ -533,10 +540,10 @@ ] } { - # [.]foo = ... => ... + # [.|?.]foo = ... => ... 'begin': '''(?x) (?= - (\\.)?[a-zA-Z_$][\\w$]* + (\\?\\.|\\.)?[a-zA-Z_$][\\w$]* \\s*(=)\\s* ((\\(([^\\(\\)]*)?\\))|[\\w$]+) \\s*=> @@ -546,8 +553,8 @@ (?<=})| ((?! \\s*{| - \\G(\\.)?[a-zA-Z_$][\\w$]*\\s*(=)\\s*\\(| - \\G(\\.)?[a-zA-Z_$][\\w$]*\\s*(=)\\s*[\\w$]+| + \\G(\\?\\.|\\.)?[a-zA-Z_$][\\w$]*\\s*(=)\\s*\\(| + \\G(\\?\\.|\\.)?[a-zA-Z_$][\\w$]*\\s*(=)\\s*[\\w$]+| \\s*/\\*|\\s*// )(?=\\s*\\S)) ''' @@ -564,10 +571,14 @@ 'name': 'meta.function.arrow.js' 'patterns': [ { - 'match': '\\G(\\.)?([a-zA-Z_$][\\w$]*)\\s*(=)' + 'match': '\\G(\\?\\.|\\.)?([a-zA-Z_$][\\w$]*)\\s*(=)' 'captures': '1': - 'name': 'meta.delimiter.method.period.js' + 'patterns': [ + { + 'include': '#period_or_optional_method' + } + ] '2': 'name': 'entity.name.function.js' '3': @@ -729,8 +740,7 @@ 'name': 'entity.name.type.instance.js' 'patterns': [ { - 'match': '\\.' - 'name': 'meta.delimiter.property.period.js' + 'include': '#period_or_optional_property' } ] } @@ -743,7 +753,7 @@ 'name': 'entity.name.type.object.console.js' 'end': '''(?x) (?<=\\)) | (?= - (?! (\\s*//)|(\\s*/\\*)|(\\s*(\\.)\\s* + (?! (\\s*//)|(\\s*/\\*)|(\\s*(\\?\\.|\\.)\\s* (assert|clear|debug|error|info|log|profile|profileEnd|time|timeEnd|warn) \\s*\\( )) \\s*\\S @@ -754,10 +764,14 @@ 'include': '#comments' } { - 'begin': '\\s*(\\.)\\s*(\\w+)\\s*(?=\\()' + 'begin': '\\s*(\\?\\.|\\.)\\s*(\\w+)\\s*(?=\\()' 'beginCaptures': '1': - 'name': 'meta.delimiter.method.period.js' + 'patterns': [ + { + 'include': '#period_or_optional_method' + } + ] '2': 'name': 'support.function.console.js' 'end': '(?<=\\))' @@ -779,7 +793,7 @@ 'end': '''(?x) (?<=E|LN10|LN2|LOG10E|LOG2E|PI|SQRT1_2|SQRT2|\\) ) | (?= - (?! (\\s*//)|(\\s*/\\*)|(\\s*\\.\\s* ( + (?! (\\s*//)|(\\s*/\\*)|(\\s*(\\?\\.|\\.)\\s* ( ((abs|acos|acosh|asin|asinh|atan|atan2|atanh|cbrt|ceil|clz32|cos|cosh|exp| expm1|floor|fround|hypot|imul|log|log10|log1p|log2|max|min|pow|random| round|sign|sin|sinh|sqrt|tan|tanh|trunc)\\s*\\( @@ -793,10 +807,14 @@ } { # Math.random() - 'begin': '\\s*(\\.)\\s*(\\w+)\\s*(?=\\()' + 'begin': '\\s*(\\?\\.|\\.)\\s*(\\w+)\\s*(?=\\()' 'beginCaptures': '1': - 'name': 'meta.delimiter.method.period.js' + 'patterns': [ + { + 'include': '#period_or_optional_method' + } + ] '2': 'name': 'support.function.math.js' 'end': '(?<=\\))' @@ -809,10 +827,14 @@ } { # Math.PI - 'match': '\\s*(\\.)\\s*(\\w+)\\b' + 'match': '\\s*(\\?\\.|\\.)\\s*(\\w+)\\b' 'captures': '1': - 'name': 'meta.delimiter.property.period.js' + 'patterns': [ + { + 'include': '#period_or_optional_property' + } + ] '2': 'name': 'support.constant.property.math.js' } @@ -826,7 +848,7 @@ 'name': 'support.class.promise.js' 'end': '''(?x) (?<=\\)) | (?= - (?! (\\s*//)|(\\s*/\\*)|(\\s*\\.\\s*(all|race|reject|resolve)\\s*\\() )\\s*\\S + (?! (\\s*//)|(\\s*/\\*)|(\\s*(\\?\\.|\\.)\\s*(all|race|reject|resolve)\\s*\\() )\\s*\\S ) ''' 'patterns': [ @@ -835,10 +857,14 @@ } { # Promise.all() - 'begin': '\\s*(\\.)\\s*(\\w+)\\s*(?=\\()' + 'begin': '\\s*(\\?\\.|\\.)\\s*(\\w+)\\s*(?=\\()' 'beginCaptures': '1': - 'name': 'meta.delimiter.method.period.js' + 'patterns': [ + { + 'include': '#period_or_optional_method' + } + ] '2': 'name': 'support.function.promise.js' 'end': '(?<=\\))' @@ -941,6 +967,7 @@ 'name': 'meta.control.yield.js' } { + # This is "..." so don't need to check "?." 'match': '(?:(?<=\\.{3})|(?>=|>>>=|\\|=' + # &&= ||= ??= + 'match': '&&=|\\|\\|=|\\?\\?=' + 'name': 'keyword.operator.assignment.compound.logical.js' + } + { + 'match': '&=|\\^=|\\|=' 'name': 'keyword.operator.assignment.compound.bitwise.js' } + { + 'match': '<<=|>>=|>>>=' + 'name': 'keyword.operator.assignment.compound.bitwise.shift.js' + } + # Bitwise shift to match >> instead of > + # Then comparison to match != instead of ! { 'match': '<<|>>>|>>' 'name': 'keyword.operator.bitwise.shift.js' @@ -1330,7 +1378,8 @@ 'name': 'keyword.operator.comparison.js' } { - 'match': '&&|!!|!|\\|\\|' + # One of: && !! ! || ?? + 'match': '&&|!!|!|\\|\\||\\?\\?' 'name': 'keyword.operator.logical.js' } { @@ -1350,7 +1399,7 @@ 'name': 'keyword.operator.increment.js' } { - 'match': '%|\\*|/|-|\\+' + 'match': '%|\\*\\*|\\*|/|-|\\+' 'name': 'keyword.operator.js' } ] @@ -1447,7 +1496,7 @@ ] } { - 'begin': '(Relay\\.QL|gql)\\s*(`)' + 'begin': '(Relay(\\?\\.|\\.)QL|gql)\\s*(`)' 'beginCaptures': '1': 'name': 'entity.name.function.js' @@ -1559,7 +1608,38 @@ 'match': '(\\.\\.\\.)([a-zA-Z_$][\\w$]*)' 'captures': '1': - 'name': 'keyword.operator.spread.js' + 'name': 'keyword.operator.rest.js' + '2': + 'name': 'variable.parameter.rest.function.js' + } + { + 'include': '$self' + } + { + 'match': '[a-zA-Z_$][\\w$]*' + 'name': 'variable.parameter.function.js' + } + ] + } + { + # Or starting with optional chaining + 'begin': '(\\?\\.)(\\()' + 'beginCaptures': + '1': + 'name': 'meta.delimiter.method.optional.js' + '2': + 'name': 'punctuation.definition.parameters.begin.bracket.round.js' + 'end': '\\)' + 'endCaptures': + '0': + 'name': 'punctuation.definition.parameters.end.bracket.round.js' + 'name': 'meta.parameters.js' + 'patterns': [ + { + 'match': '(\\.\\.\\.)([a-zA-Z_$][\\w$]*)' + 'captures': + '1': + 'name': 'keyword.operator.rest.js' '2': 'name': 'variable.parameter.rest.function.js' } @@ -1662,96 +1742,181 @@ 'method_calls': 'patterns': [ { - # .methodCall(arg1, "arg2", [...]) - 'begin': '(\\.)\\s*([\\w$]+)\\s*(?=\\()' + # [?].methodCall(arg1, "arg2", [...]) + 'begin': '(\\?\\.|\\.)\\s*([\\w$]+)\\s*(?=\\()' 'beginCaptures': '1': - 'name': 'meta.delimiter.method.period.js' + 'patterns': [ + { + 'include': '#period_or_optional_method' + } + ] '2': 'patterns': [ { - 'match': '''(?x) - \\bon(Rowsinserted|Rowsdelete|Rowenter|Rowexit|Resize|Resizestart|Resizeend|Reset| - Readystatechange|Mouseout|Mouseover|Mousedown|Mouseup|Mousemove| - Before(cut|deactivate|unload|update|paste|print|editfocus|activate)| - Blur|Scrolltop|Submit|Select|Selectstart|Selectionchange|Hover|Help| - Change|Contextmenu|Controlselect|Cut|Cellchange|Clock|Close|Deactivate| - Datasetchanged|Datasetcomplete|Dataavailable|Drop|Drag|Dragstart|Dragover| - Dragdrop|Dragenter|Dragend|Dragleave|Dblclick|Unload|Paste|Propertychange|Error| - Errorupdate|Keydown|Keyup|Keypress|Focus|Load|Activate|Afterupdate|Afterprint|Abort)\\b - ''' - 'name': 'support.function.event-handler.js' + 'include': '#method_names' } + ] + 'end': '(?<=\\))' + 'name': 'meta.method-call.js' + 'patterns': [ + { + 'include': '#arguments' + } + ] + } + { + # [?].methodCall?.(arg1, "arg2", [...]) + 'begin': '(\\?\\.|\\.)\\s*([\\w$]+)\\s*(\\?\\.)\\s*(?=\\()' + 'beginCaptures': + '1': + 'patterns': [ { - 'match': '''(?x) - \\b(catch|finally|then|shift|showModelessDialog|showModalDialog|showHelp|scroll|scrollX|scrollByPages| - scrollByLines|scrollY|scrollTo|stop|strike|sizeToContent|sidebar|signText|sort| - sup|sub|substr|substring|splice|split|send|set(Milliseconds|Seconds|Minutes|Hours| - Month|Year|FullYear|Date|UTC(Milliseconds|Seconds|Minutes|Hours|Month|FullYear|Date)| - Time|Hotkeys|Cursor|ZOptions|Active|Resizable|RequestHeader)|search|slice| - savePreferences|small|home|handleEvent|navigate|char|charCodeAt|charAt|concat| - contextual|confirm|compile|clear|captureEvents|call|createStyleSheet|createPopup| - createEventObject|to(GMTString|UTCString|String|Source|UpperCase|LowerCase|LocaleString)| - test|taint|taintEnabled|indexOf|italics|disableExternalCapture|dump|detachEvent|unshift| - untaint|unwatch|updateCommands|join|javaEnabled|pop|push|plugins.refresh|paddings|parse|stringify| - print|prompt|preference|enableExternalCapture|exec|execScript|valueOf|UTC|find|file| - fileModifiedDate|fileSize|fileCreatedDate|fileUpdatedDate|fixed|fontsize|fontcolor| - forward|fromCharCode|watch|link|load|lastIndexOf|anchor|attachEvent|atob|apply|alert| - abort|routeEvents|resize|resizeBy|resizeTo|recalc|returnValue|replace|reverse|reload| - releaseCapture|releaseEvents|go|get(Milliseconds|Seconds|Minutes|Hours|Month|Day|Year|FullYear| - Time|Date|TimezoneOffset|UTC(Milliseconds|Seconds|Minutes|Hours|Day|Month|FullYear|Date)| - Attention|Selection|ResponseHeader|AllResponseHeaders)|moveBy|moveBelow|moveTo| - moveToAbsolute|moveAbove|mergeAttributes|match|margins|btoa|big|bold|borderWidths|blink|back)\\b - ''' - 'name': 'support.function.js' + 'include': '#period_or_optional_method' } + ] + '2': + 'patterns': [ + { + 'include': '#method_names' + } + ] + '3': + 'name': 'meta.delimiter.method.optional.js' + 'end': '(?<=\\))' + 'name': 'meta.method-call.js' + 'patterns': [ + { + 'include': '#arguments' + } + ] + } + { + # [array]?.(arg1, "arg2", [...]) + # The array itself is not in the method-call scope, maybe later + 'begin': '(?<=\\])\\s*(\\?\\.)\\s*(?=\\()' + 'beginCaptures': + '1': + 'name': 'meta.delimiter.method.optional.js' + 'end': '(?<=\\))' + 'name': 'meta.method-call.js' + 'patterns': [ + { + 'include': '#arguments' + } + ] + } + ] + 'method_names': + 'patterns': [ + { + 'match': '''(?x) + \\bon(Rowsinserted|Rowsdelete|Rowenter|Rowexit|Resize|Resizestart|Resizeend|Reset| + Readystatechange|Mouseout|Mouseover|Mousedown|Mouseup|Mousemove| + Before(cut|deactivate|unload|update|paste|print|editfocus|activate)| + Blur|Scrolltop|Submit|Select|Selectstart|Selectionchange|Hover|Help| + Change|Contextmenu|Controlselect|Cut|Cellchange|Clock|Close|Deactivate| + Datasetchanged|Datasetcomplete|Dataavailable|Drop|Drag|Dragstart|Dragover| + Dragdrop|Dragenter|Dragend|Dragleave|Dblclick|Unload|Paste|Propertychange|Error| + Errorupdate|Keydown|Keyup|Keypress|Focus|Load|Activate|Afterupdate|Afterprint|Abort)\\b + ''' + 'name': 'support.function.event-handler.js' + } + { + 'match': '''(?x) + \\b(catch|finally|then|shift|showModelessDialog|showModalDialog|showHelp|scroll|scrollX|scrollByPages| + scrollByLines|scrollY|scrollTo|stop|strike|sizeToContent|sidebar|signText|sort| + sup|sub|substr|substring|splice|split|send|set(Milliseconds|Seconds|Minutes|Hours| + Month|Year|FullYear|Date|UTC(Milliseconds|Seconds|Minutes|Hours|Month|FullYear|Date)| + Time|Hotkeys|Cursor|ZOptions|Active|Resizable|RequestHeader)|search|slice| + savePreferences|small|home|handleEvent|navigate|char|charCodeAt|charAt|concat| + contextual|confirm|compile|clear|captureEvents|call|createStyleSheet|createPopup| + createEventObject|to(GMTString|UTCString|String|Source|UpperCase|LowerCase|LocaleString)| + test|taint|taintEnabled|indexOf|italics|disableExternalCapture|dump|detachEvent|unshift| + untaint|unwatch|updateCommands|join|javaEnabled|pop|push|plugins.refresh|paddings|parse|stringify| + print|prompt|preference|enableExternalCapture|exec|execScript|valueOf|UTC|find|file| + fileModifiedDate|fileSize|fileCreatedDate|fileUpdatedDate|fixed|fontsize|fontcolor| + forward|fromCharCode|watch|link|load|lastIndexOf|anchor|attachEvent|atob|apply|alert| + abort|routeEvents|resize|resizeBy|resizeTo|recalc|returnValue|replace|reverse|reload| + releaseCapture|releaseEvents|go|get(Milliseconds|Seconds|Minutes|Hours|Month|Day|Year|FullYear| + Time|Date|TimezoneOffset|UTC(Milliseconds|Seconds|Minutes|Hours|Day|Month|FullYear|Date)| + Attention|Selection|ResponseHeader|AllResponseHeaders)|moveBy|moveBelow|moveTo| + moveToAbsolute|moveAbove|mergeAttributes|match|margins|btoa|big|bold|borderWidths|blink|back)\\b + ''' + 'name': 'support.function.js' + } + { + 'match': '''(?x) + \\b(acceptNode|add|addEventListener|addTextTrack|adoptNode|after|animate|append| + appendChild|appendData|before|blur|canPlayType|captureStream| + caretPositionFromPoint|caretRangeFromPoint|checkValidity|clear|click| + cloneContents|cloneNode|cloneRange|close|closest|collapse| + compareBoundaryPoints|compareDocumentPosition|comparePoint|contains| + convertPointFromNode|convertQuadFromNode|convertRectFromNode|createAttribute| + createAttributeNS|createCaption|createCDATASection|createComment| + createContextualFragment|createDocument|createDocumentFragment| + createDocumentType|createElement|createElementNS|createEntityReference| + createEvent|createExpression|createHTMLDocument|createNodeIterator| + createNSResolver|createProcessingInstruction|createRange|createShadowRoot| + createTBody|createTextNode|createTFoot|createTHead|createTreeWalker|delete| + deleteCaption|deleteCell|deleteContents|deleteData|deleteRow|deleteTFoot| + deleteTHead|detach|disconnect|dispatchEvent|elementFromPoint|elementsFromPoint| + enableStyleSheetsForSet|entries|evaluate|execCommand|exitFullscreen| + exitPointerLock|expand|extractContents|fastSeek|firstChild|focus|forEach|get| + getAll|getAnimations|getAttribute|getAttributeNames|getAttributeNode| + getAttributeNodeNS|getAttributeNS|getBoundingClientRect|getBoxQuads| + getClientRects|getContext|getDestinationInsertionPoints|getElementById| + getElementsByClassName|getElementsByName|getElementsByTagName| + getElementsByTagNameNS|getItem|getNamedItem|getSelection|getStartDate| + getVideoPlaybackQuality|has|hasAttribute|hasAttributeNS|hasAttributes| + hasChildNodes|hasFeature|hasFocus|importNode|initEvent|insertAdjacentElement| + insertAdjacentHTML|insertAdjacentText|insertBefore|insertCell|insertData| + insertNode|insertRow|intersectsNode|isDefaultNamespace|isEqualNode| + isPointInRange|isSameNode|item|key|keys|lastChild|load|lookupNamespaceURI| + lookupPrefix|matches|move|moveAttribute|moveAttributeNode|moveChild| + moveNamedItem|namedItem|nextNode|nextSibling|normalize|observe|open| + parentNode|pause|play|postMessage|prepend|preventDefault|previousNode| + previousSibling|probablySupportsContext|queryCommandEnabled| + queryCommandIndeterm|queryCommandState|queryCommandSupported|queryCommandValue| + querySelector|querySelectorAll|registerContentHandler|registerElement| + registerProtocolHandler|releaseCapture|releaseEvents|remove|removeAttribute| + removeAttributeNode|removeAttributeNS|removeChild|removeEventListener| + removeItem|replace|replaceChild|replaceData|replaceWith|reportValidity| + requestFullscreen|requestPointerLock|reset|scroll|scrollBy|scrollIntoView| + scrollTo|seekToNextFrame|select|selectNode|selectNodeContents|set|setAttribute| + setAttributeNode|setAttributeNodeNS|setAttributeNS|setCapture| + setCustomValidity|setEnd|setEndAfter|setEndBefore|setItem|setNamedItem| + setRangeText|setSelectionRange|setSinkId|setStart|setStartAfter|setStartBefore| + slice|splitText|stepDown|stepUp|stopImmediatePropagation|stopPropagation| + submit|substringData|supports|surroundContents|takeRecords|terminate|toBlob| + toDataURL|toggle|toString|values|write|writeln)\\b + ''' + 'name': 'support.function.dom.js' + } + { + 'match': "[a-zA-Z_$][\\w$]*" + 'name': 'entity.name.function.js' + } + { + 'match': '\\d[\\w$]*' + 'name': 'invalid.illegal.identifier.js' + } + ] + 'function_calls': + 'patterns': [ + { + # functionCall(arg1, "arg2", [...]) + 'begin': '([\\w$]+)\\s*(?=\\()' + 'beginCaptures': + '1': + 'patterns': [ { 'match': '''(?x) - \\b(acceptNode|add|addEventListener|addTextTrack|adoptNode|after|animate|append| - appendChild|appendData|before|blur|canPlayType|captureStream| - caretPositionFromPoint|caretRangeFromPoint|checkValidity|clear|click| - cloneContents|cloneNode|cloneRange|close|closest|collapse| - compareBoundaryPoints|compareDocumentPosition|comparePoint|contains| - convertPointFromNode|convertQuadFromNode|convertRectFromNode|createAttribute| - createAttributeNS|createCaption|createCDATASection|createComment| - createContextualFragment|createDocument|createDocumentFragment| - createDocumentType|createElement|createElementNS|createEntityReference| - createEvent|createExpression|createHTMLDocument|createNodeIterator| - createNSResolver|createProcessingInstruction|createRange|createShadowRoot| - createTBody|createTextNode|createTFoot|createTHead|createTreeWalker|delete| - deleteCaption|deleteCell|deleteContents|deleteData|deleteRow|deleteTFoot| - deleteTHead|detach|disconnect|dispatchEvent|elementFromPoint|elementsFromPoint| - enableStyleSheetsForSet|entries|evaluate|execCommand|exitFullscreen| - exitPointerLock|expand|extractContents|fastSeek|firstChild|focus|forEach|get| - getAll|getAnimations|getAttribute|getAttributeNames|getAttributeNode| - getAttributeNodeNS|getAttributeNS|getBoundingClientRect|getBoxQuads| - getClientRects|getContext|getDestinationInsertionPoints|getElementById| - getElementsByClassName|getElementsByName|getElementsByTagName| - getElementsByTagNameNS|getItem|getNamedItem|getSelection|getStartDate| - getVideoPlaybackQuality|has|hasAttribute|hasAttributeNS|hasAttributes| - hasChildNodes|hasFeature|hasFocus|importNode|initEvent|insertAdjacentElement| - insertAdjacentHTML|insertAdjacentText|insertBefore|insertCell|insertData| - insertNode|insertRow|intersectsNode|isDefaultNamespace|isEqualNode| - isPointInRange|isSameNode|item|key|keys|lastChild|load|lookupNamespaceURI| - lookupPrefix|matches|move|moveAttribute|moveAttributeNode|moveChild| - moveNamedItem|namedItem|nextNode|nextSibling|normalize|observe|open| - parentNode|pause|play|postMessage|prepend|preventDefault|previousNode| - previousSibling|probablySupportsContext|queryCommandEnabled| - queryCommandIndeterm|queryCommandState|queryCommandSupported|queryCommandValue| - querySelector|querySelectorAll|registerContentHandler|registerElement| - registerProtocolHandler|releaseCapture|releaseEvents|remove|removeAttribute| - removeAttributeNode|removeAttributeNS|removeChild|removeEventListener| - removeItem|replace|replaceChild|replaceData|replaceWith|reportValidity| - requestFullscreen|requestPointerLock|reset|scroll|scrollBy|scrollIntoView| - scrollTo|seekToNextFrame|select|selectNode|selectNodeContents|set|setAttribute| - setAttributeNode|setAttributeNodeNS|setAttributeNS|setCapture| - setCustomValidity|setEnd|setEndAfter|setEndBefore|setItem|setNamedItem| - setRangeText|setSelectionRange|setSinkId|setStart|setStartAfter|setStartBefore| - slice|splitText|stepDown|stepUp|stopImmediatePropagation|stopPropagation| - submit|substringData|supports|surroundContents|takeRecords|terminate|toBlob| - toDataURL|toggle|toString|values|write|writeln)\\b + \\b(isNaN|isFinite|eval|uneval|parseInt|parseFloat|decodeURI| + decodeURIComponent|encodeURI|encodeURIComponent|escape|unescape| + require|set(Interval|Timeout)|clear(Interval|Timeout))\\b ''' - 'name': 'support.function.dom.js' + 'name': 'support.function.js' } { 'match': "[a-zA-Z_$][\\w$]*" @@ -1763,19 +1928,16 @@ } ] 'end': '(?<=\\))' - 'name': 'meta.method-call.js' + 'name': 'meta.function-call.js' 'patterns': [ { 'include': '#arguments' } ] } - ] - 'function_calls': - 'patterns': [ { - # functionCall(arg1, "arg2", [...]) - 'begin': '([\\w$]+)\\s*(?=\\()' + # functionCall?.(arg1, "arg2", [...]) + 'begin': '([\\w$]+)\\s*(\\?\\.)(?=\\()' 'beginCaptures': '1': 'patterns': [ @@ -1796,6 +1958,8 @@ 'name': 'invalid.illegal.identifier.js' } ] + '2': + 'name': 'meta.delimiter.method.optional.js' 'end': '(?<=\\))' 'name': 'meta.function-call.js' 'patterns': [ @@ -1807,61 +1971,113 @@ ] 'objects': 'patterns': [ + # "Obj." is already enough to prove objectness, + # however "Obj.[8]" is impossible, and + # "Obj?.()" is a function { # OBJ in OBJ.prop, OBJ.methodCall() - 'match': '[A-Z][A-Z0-9_$]*(?=\\s*\\.\\s*[a-zA-Z_$]\\w*)' + 'match': '[A-Z][A-Z0-9_$]*(?=\\s*\\??\\.\\s*[a-zA-Z_$]\\w*)' 'name': 'constant.other.object.js' } { # obj in obj.prop, obj.methodCall() - 'match': '[a-zA-Z_$][\\w$]*(?=\\s*\\.\\s*[a-zA-Z_$]\\w*)' + 'match': '[a-zA-Z_$][\\w$]*(?=\\s*\\??\\.\\s*[a-zA-Z_$]\\w*)' + 'name': 'variable.other.object.js' + } + { + # OBJ in OBJ[8], OBJ?.[8] + 'match': '[A-Z][A-Z0-9_$]*(?=\\s*(\\?\\.)?\\s*\\[)' + 'name': 'constant.other.object.js' + } + { + # obj in obj[8], obj?.[8] + 'match': '[a-zA-Z_$][\\w$]*(?=\\s*(\\?\\.)?\\s*\\[)' 'name': 'variable.other.object.js' } ] 'properties': 'patterns': [ + # object.prop includes object?.prop + { + # Specifically ?.[], for example divArray?.[8] + 'begin': '(\\?\\.)\\s*(\\[)' + 'beginCaptures': + '1': + 'name': 'meta.delimiter.property.optional.js' + '2': + 'name': 'meta.brace.square.js' + 'end': '\\]' + 'endCaptures': + '0': + 'name': 'meta.brace.square.js' + 'patterns': [ + { + 'include': '$self' + } + ] + } { # PROP1 in obj.PROP1.prop2, func().PROP1.prop2 - 'match': '(\\.)\\s*([A-Z][A-Z0-9_$]*\\b\\$*)(?=\\s*\\.\\s*[a-zA-Z_$]\\w*)' + 'match': '(\\?\\.|\\.)\\s*([A-Z][A-Z0-9_$]*\\b\\$*)(?=\\s*(\\?\\.|\\.)\\s*[a-zA-Z_$]\\w*)' 'captures': '1': - 'name': 'meta.delimiter.property.period.js' + 'patterns': [ + { + 'include': '#period_or_optional_property' + } + ] '2': 'name': 'constant.other.object.property.js' } { # prop1 in obj.prop1.prop2, func().prop1.prop2 - 'match': '(\\.)\\s*(\\$*[a-zA-Z_$][\\w$]*)(?=\\s*\\.\\s*[a-zA-Z_$]\\w*)' + 'match': '(\\?\\.|\\.)\\s*(\\$*[a-zA-Z_$][\\w$]*)(?=\\s*(\\?\\.|\\.)\\s*[a-zA-Z_$]\\w*)' 'captures': '1': - 'name': 'meta.delimiter.property.period.js' + 'patterns': [ + { + 'include': '#period_or_optional_property' + } + ] '2': 'name': 'variable.other.object.property.js' } { # PROP in obj.PROP, func().PROP - 'match': '(\\.)\\s*([A-Z][A-Z0-9_$]*\\b\\$*)' + 'match': '(\\?\\.|\\.)\\s*([A-Z][A-Z0-9_$]*\\b\\$*)' 'captures': '1': - 'name': 'meta.delimiter.property.period.js' + 'patterns': [ + { + 'include': '#period_or_optional_property' + } + ] '2': 'name': 'constant.other.property.js' } { # prop in obj.prop, func().prop - 'match': '(\\.)\\s*(\\$*[a-zA-Z_$][\\w$]*)' + 'match': '(\\?\\.|\\.)\\s*(\\$*[a-zA-Z_$][\\w$]*)' 'captures': '1': - 'name': 'meta.delimiter.property.period.js' + 'patterns': [ + { + 'include': '#period_or_optional_property' + } + ] '2': 'name': 'variable.other.property.js' } { # 123illegal in obj.123illegal, func().123illegal - 'match': '(\\.)\\s*([0-9][\\w$]*)' + 'match': '(\\?\\.|\\.)\\s*([0-9][\\w$]*)' 'captures': '1': - 'name': 'meta.delimiter.property.period.js' + 'patterns': [ + { + 'include': '#period_or_optional_property' + } + ] '2': 'name': 'invalid.illegal.identifier.js' } @@ -2026,3 +2242,25 @@ ] } ] + 'period_or_optional_property': + 'patterns': [ + { + 'match': '\\.' + 'name': 'meta.delimiter.property.period.js' + } + { + 'match': '\\?\\.' + 'name': 'meta.delimiter.property.optional.js' + } + ] + 'period_or_optional_method': + 'patterns': [ + { + 'match': '\\.' + 'name': 'meta.delimiter.method.period.js' + } + { + 'match': '\\?\\.' + 'name': 'meta.delimiter.method.optional.js' + } + ] diff --git a/packages/language-javascript/grammars/tree-sitter-javascript.cson b/packages/language-javascript/grammars/tree-sitter-javascript.cson index 86919162fc..465b0437b3 100644 --- a/packages/language-javascript/grammars/tree-sitter-javascript.cson +++ b/packages/language-javascript/grammars/tree-sitter-javascript.cson @@ -184,9 +184,13 @@ scopes: '"*="': 'keyword.operator.js' '"/="': 'keyword.operator.js' '"%="': 'keyword.operator.js' + '"**="': 'keyword.operator.js' '"<<="': 'keyword.operator.js' '">>="': 'keyword.operator.js' '">>>="': 'keyword.operator.js' + '"&&="': 'keyword.operator.js' + '"||="': 'keyword.operator.js' + '"??="': 'keyword.operator.js' '"&="': 'keyword.operator.js' '"^="': 'keyword.operator.js' '"|="': 'keyword.operator.js' @@ -196,6 +200,7 @@ scopes: '"*"': 'keyword.operator.js' '"/"': 'keyword.operator.js' '"%"': 'keyword.operator.js' + '"**"': 'keyword.operator.js' '"=="': 'keyword.operator.js' '"==="': 'keyword.operator.js' '"!="': 'keyword.operator.js' @@ -204,25 +209,28 @@ scopes: '"<="': 'keyword.operator.js' '">"': 'keyword.operator.js' '"<"': 'keyword.operator.js' - '":"': 'keyword.operator.js' - '"?"': 'keyword.operator.js' '"&&"': 'keyword.operator.js' '"||"': 'keyword.operator.js' + '"??"': 'keyword.operator.js' '"&"': 'keyword.operator.js' - '"~"': 'keyword.operator.js' '"^"': 'keyword.operator.js' + '"|"': 'keyword.operator.js' + '"~"': 'keyword.operator.js' '">>"': 'keyword.operator.js' '">>>"': 'keyword.operator.js' '"<<"': 'keyword.operator.js' - '"|"': 'keyword.operator.js' '"++"': 'keyword.operator.js' '"--"': 'keyword.operator.js' '"..."': 'keyword.operator.spread.js' + '"?"': 'keyword.operator.js' + '":"': 'keyword.operator.js' '"in"': 'keyword.operator.in' '"instanceof"': 'keyword.operator.instanceof' '"of"': 'keyword.operator.of' '"new"': 'keyword.operator.new' + '"delete"': 'keyword.operator.delete' + '"void"': 'keyword.operator.void' '"typeof"': 'keyword.operator.typeof' '"get"': 'keyword.operator.setter' @@ -254,7 +262,6 @@ scopes: '"async"': 'keyword.control' '"await"': 'keyword.control' '"debugger"': 'keyword.control' - '"delete"': 'keyword.control' 'jsx_attribute > property_identifier': 'entity.other.attribute-name' 'jsx_opening_element > identifier': 'entity.name.tag' diff --git a/packages/language-javascript/spec/javascript-spec.coffee b/packages/language-javascript/spec/javascript-spec.coffee index 192850ec22..15650559c2 100644 --- a/packages/language-javascript/spec/javascript-spec.coffee +++ b/packages/language-javascript/spec/javascript-spec.coffee @@ -554,7 +554,7 @@ describe "JavaScript grammar", -> expect(tokens[1]).toEqual value: '--', scopes: ['source.js', 'keyword.operator.decrement.js'] describe "logical", -> - operators = ["&&", "||", "!"] + operators = ["&&", "||", "!", "??"] it "tokenizes them", -> for operator in operators @@ -598,7 +598,7 @@ describe "JavaScript grammar", -> expect(tokens[2]).toEqual value: ' b', scopes: ['source.js'] describe "arithmetic", -> - operators = ["*", "/", "-", "%", "+"] + operators = ["*", "/", "-", "%", "+", "**"] it "tokenizes them", -> for operator in operators @@ -623,22 +623,39 @@ describe "JavaScript grammar", -> describe "compound", -> it "tokenizes them", -> - operators = ["+=", "-=", "*=", "/=", "%="] + operators = ["+=", "-=", "*=", "/=", "%=", "**="] for operator in operators {tokens} = grammar.tokenizeLine('a ' + operator + ' b') expect(tokens[0]).toEqual value: 'a ', scopes: ['source.js'] expect(tokens[1]).toEqual value: operator, scopes: ['source.js', 'keyword.operator.assignment.compound.js'] expect(tokens[2]).toEqual value: ' b', scopes: ['source.js'] + describe "logical", -> + it "tokenizes them", -> + operators = ["||=", "&&=", "??="] + for operator in operators + {tokens} = grammar.tokenizeLine('a ' + operator + ' b') + expect(tokens[0]).toEqual value: 'a ', scopes: ['source.js'] + expect(tokens[1]).toEqual value: operator, scopes: ['source.js', 'keyword.operator.assignment.compound.logical.js'] + expect(tokens[2]).toEqual value: ' b', scopes: ['source.js'] + describe "bitwise", -> it "tokenizes them", -> - operators = ["<<=", ">>=", ">>>=", "&=", "^=", "|="] + operators = ["&=", "^=", "|="] for operator in operators {tokens} = grammar.tokenizeLine('a ' + operator + ' b') expect(tokens[0]).toEqual value: 'a ', scopes: ['source.js'] expect(tokens[1]).toEqual value: operator, scopes: ['source.js', 'keyword.operator.assignment.compound.bitwise.js'] expect(tokens[2]).toEqual value: ' b', scopes: ['source.js'] + it "tokenizes bitwise shift", -> + operators = ["<<=", ">>=", ">>>="] + for operator in operators + {tokens} = grammar.tokenizeLine('a ' + operator + ' b') + expect(tokens[0]).toEqual value: 'a ', scopes: ['source.js'] + expect(tokens[1]).toEqual value: operator, scopes: ['source.js', 'keyword.operator.assignment.compound.bitwise.shift.js'] + expect(tokens[2]).toEqual value: ' b', scopes: ['source.js'] + describe "constants", -> it "tokenizes ALL_CAPS variables as constants", -> {tokens} = grammar.tokenizeLine('var MY_COOL_VAR = 42;') @@ -1604,9 +1621,13 @@ describe "JavaScript grammar", -> it "tokenizes the rest parameter", -> {tokens} = grammar.tokenizeLine('(...args) => args[0]') - expect(tokens[1]).toEqual value: '...', scopes: ['source.js', 'meta.function.arrow.js', 'meta.parameters.js', 'keyword.operator.spread.js'] + expect(tokens[1]).toEqual value: '...', scopes: ['source.js', 'meta.function.arrow.js', 'meta.parameters.js', 'keyword.operator.rest.js'] expect(tokens[2]).toEqual value: 'args', scopes: ['source.js', 'meta.function.arrow.js', 'meta.parameters.js', 'variable.parameter.rest.function.js'] + {tokens} = grammar.tokenizeLine('(c, ...val) => c + val') + expect(tokens[4]).toEqual value: '...', scopes: ['source.js', 'meta.function.arrow.js', 'meta.parameters.js', 'keyword.operator.rest.js'] + expect(tokens[5]).toEqual value: 'val', scopes: ['source.js', 'meta.function.arrow.js', 'meta.parameters.js', 'variable.parameter.rest.function.js'] + it "tokenizes illegal parameters", -> {tokens} = grammar.tokenizeLine('0abc => {}') expect(tokens[0]).toEqual value: '0abc', scopes: ['source.js', 'meta.function.arrow.js', 'meta.parameters.js', 'invalid.illegal.identifier.js'] @@ -1889,6 +1910,64 @@ describe "JavaScript grammar", -> expect(tokens[1]).toEqual value: '.', scopes: ['source.js', 'meta.delimiter.property.period.js'] expect(tokens[2]).toEqual value: 'C', scopes: ['source.js', 'constant.other.property.js'] + it "tokenizes the optional chaining operator", -> + {tokens} = grammar.tokenizeLine('obj?.prop') + expect(tokens[0]).toEqual value: 'obj', scopes: ['source.js', 'variable.other.object.js'] + expect(tokens[1]).toEqual value: '?.', scopes: ['source.js', 'meta.delimiter.property.optional.js'] + expect(tokens[2]).toEqual value: 'prop', scopes: ['source.js', 'variable.other.property.js'] + + {tokens} = grammar.tokenizeLine('obj?.$_') + expect(tokens[0]).toEqual value: 'obj', scopes: ['source.js', 'variable.other.object.js'] + expect(tokens[1]).toEqual value: '?.', scopes: ['source.js', 'meta.delimiter.property.optional.js'] + expect(tokens[2]).toEqual value: '$_', scopes: ['source.js', 'variable.other.property.js'] + + {tokens} = grammar.tokenizeLine('a()?.b()') + expect(tokens[2]).toEqual value: ')', scopes: ['source.js', 'meta.function-call.js', 'meta.arguments.js', 'punctuation.definition.arguments.end.bracket.round.js'] + expect(tokens[3]).toEqual value: '?.', scopes: ['source.js', 'meta.method-call.js', 'meta.delimiter.method.optional.js'] + expect(tokens[4]).toEqual value: 'b', scopes: ['source.js', 'meta.method-call.js', 'entity.name.function.js'] + + {tokens} = grammar.tokenizeLine('a()?.MY_CONSTANT') + expect(tokens[0]).toEqual value: 'a', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'] + expect(tokens[3]).toEqual value: '?.', scopes: ['source.js', 'meta.delimiter.property.optional.js'] + expect(tokens[4]).toEqual value: 'MY_CONSTANT', scopes: ['source.js', 'constant.other.property.js'] + + {tokens} = grammar.tokenizeLine('a.b?.()') + expect(tokens[0]).toEqual value: 'a', scopes: ['source.js', 'variable.other.object.js'] + expect(tokens[1]).toEqual value: '.', scopes: ['source.js', 'meta.method-call.js', 'meta.delimiter.method.period.js'] + expect(tokens[2]).toEqual value: 'b', scopes: ['source.js', 'meta.method-call.js', 'entity.name.function.js'] + expect(tokens[3]).toEqual value: '?.', scopes: ['source.js', 'meta.method-call.js', 'meta.delimiter.method.optional.js'] + + {tokens} = grammar.tokenizeLine('a?.b?.()') + expect(tokens[0]).toEqual value: 'a', scopes: ['source.js', 'variable.other.object.js'] + expect(tokens[1]).toEqual value: '?.', scopes: ['source.js', 'meta.method-call.js', 'meta.delimiter.method.optional.js'] + expect(tokens[2]).toEqual value: 'b', scopes: ['source.js', 'meta.method-call.js', 'entity.name.function.js'] + expect(tokens[3]).toEqual value: '?.', scopes: ['source.js', 'meta.method-call.js', 'meta.delimiter.method.optional.js'] + + {tokens} = grammar.tokenizeLine('a?.[3]') + expect(tokens[0]).toEqual value: 'a', scopes: ['source.js', 'variable.other.object.js'] + expect(tokens[1]).toEqual value: '?.', scopes: ['source.js', 'meta.delimiter.property.optional.js'] + expect(tokens[2]).toEqual value: '[', scopes: ['source.js', 'meta.brace.square.js'] + + {tokens} = grammar.tokenizeLine('a()?.[1]') + expect(tokens[0]).toEqual value: 'a', scopes: ['source.js', 'meta.function-call.js', 'entity.name.function.js'] + expect(tokens[3]).toEqual value: '?.', scopes: ['source.js', 'meta.delimiter.property.optional.js'] + expect(tokens[4]).toEqual value: '[', scopes: ['source.js', 'meta.brace.square.js'] + + {tokens} = grammar.tokenizeLine('a[4]?.()') + expect(tokens[0]).toEqual value: 'a', scopes: ['source.js', 'variable.other.object.js'] + expect(tokens[4]).toEqual value: '?.', scopes: ['source.js', 'meta.method-call.js', 'meta.delimiter.method.optional.js'] + + {tokens} = grammar.tokenizeLine('a[1]?.b()') + expect(tokens[0]).toEqual value: 'a', scopes: ['source.js', 'variable.other.object.js'] + expect(tokens[4]).toEqual value: '?.', scopes: ['source.js', 'meta.method-call.js', 'meta.delimiter.method.optional.js'] + expect(tokens[5]).toEqual value: 'b', scopes: ['source.js', 'meta.method-call.js', 'entity.name.function.js'] + + {tokens} = grammar.tokenizeLine('a[5]?.b') + expect(tokens[0]).toEqual value: 'a', scopes: ['source.js', 'variable.other.object.js'] + expect(tokens[4]).toEqual value: '?.', scopes: ['source.js', 'meta.delimiter.property.optional.js'] + expect(tokens[5]).toEqual value: 'b', scopes: ['source.js', 'variable.other.property.js'] + + describe "strings and functions", -> it "doesn't confuse them", -> {tokens} = grammar.tokenizeLine("'a'.b(':c(d)')") @@ -1951,11 +2030,6 @@ describe "JavaScript grammar", -> expect(tokens[1]).toEqual value: ' foo ', scopes: ['source.js', 'comment.block.documentation.js'] expect(tokens[2]).toEqual value: '*/', scopes: ['source.js', 'comment.block.documentation.js', 'punctuation.definition.comment.end.js'] - it "tokenizes // comments", -> - {tokens} = grammar.tokenizeLine('// comment') - expect(tokens[0]).toEqual value: '//', scopes: ['source.js', 'comment.line.double-slash.js', 'punctuation.definition.comment.js'] - expect(tokens[1]).toEqual value: ' comment', scopes: ['source.js', 'comment.line.double-slash.js'] - it "tokenizes comments inside constant definitions", -> {tokens} = grammar.tokenizeLine('const a, // comment') expect(tokens[0]).toEqual value: 'const', scopes: ['source.js', 'storage.type.const.js']