diff --git a/CHANGELOG.md b/CHANGELOG.md index abd0bcef3..a12d73057 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - Fix issue where the server would crash if the project contains an OCaml file with a syntax error. - Add configuration option for suppressing the "Do you want to start a build?" prompt. - Add configuration option for autostarting the Code Analyzer. +- Sync with latest parser/printer. ## 1.3.0 diff --git a/analysis/tests/src/expected/Completion.res.txt b/analysis/tests/src/expected/Completion.res.txt index f34dd08a2..4b7b2c1ac 100644 --- a/analysis/tests/src/expected/Completion.res.txt +++ b/analysis/tests/src/expected/Completion.res.txt @@ -937,6 +937,8 @@ Completable: Cpath Value[ForAuto, a] Complete src/Completion.res 234:34 posCursor:[234:34] posNoWhite:[234:33] Found expr:[234:18->234:36] +Pexp_apply ...__ghost__[0:-1->0:-1] (...[234:18->234:34], ...[234:34->234:36]) +posCursor:[234:34] posNoWhite:[234:33] Found expr:[234:18->234:34] Pexp_apply ...__ghost__[0:-1->0:-1] (...[234:18->234:32], ...[234:32->234:34]) posCursor:[234:34] posNoWhite:[234:33] Found expr:[234:32->234:34] Pexp_ident na:[234:32->234:34] @@ -1432,6 +1434,8 @@ Completable: Cpath Value[AndThatOther, T] Complete src/Completion.res 378:24 posCursor:[378:24] posNoWhite:[378:23] Found expr:[378:12->378:26] +Pexp_apply ...__ghost__[0:-1->0:-1] (...[378:12->378:24], ...[378:24->378:26]) +posCursor:[378:24] posNoWhite:[378:23] Found expr:[378:12->378:24] Pexp_apply ...__ghost__[0:-1->0:-1] (...[378:12->378:16], ...[378:16->378:24]) posCursor:[378:24] posNoWhite:[378:23] Found expr:[378:16->378:24] Pexp_ident ForAuto.:[378:16->378:24] @@ -1452,6 +1456,8 @@ Completable: Cpath Value[ForAuto, ""] Complete src/Completion.res 381:38 posCursor:[381:38] posNoWhite:[381:37] Found expr:[381:12->381:41] +Pexp_apply ...__ghost__[0:-1->0:-1] (...[381:12->381:39], ...[381:39->381:41]) +posCursor:[381:38] posNoWhite:[381:37] Found expr:[381:12->381:39] Pexp_apply ...__ghost__[0:-1->0:-1] (...[381:12->381:19], ...[381:19->381:39]) posCursor:[381:38] posNoWhite:[381:37] Found expr:[381:19->381:39] Pexp_send [381:38->381:38] e:[381:19->381:36] @@ -1472,6 +1478,10 @@ Completable: Cpath Value[FAO, forAutoObject][""] Complete src/Completion.res 384:24 posCursor:[384:24] posNoWhite:[384:23] Found expr:[384:11->384:26] +Pexp_apply ...__ghost__[0:-1->0:-1] (...[384:11->384:24], ...[384:24->384:26]) +posCursor:[384:24] posNoWhite:[384:23] Found expr:[384:11->384:24] +Pexp_apply ...__ghost__[0:-1->0:-1] (...[384:11->384:14], ...[384:14->384:24]) +posCursor:[384:24] posNoWhite:[384:23] Found expr:[384:14->384:24] Pexp_field [384:14->384:23] _:[384:24->384:24] Completable: Cpath Value[funRecord]."" [{ diff --git a/analysis/tests/src/expected/Hover.res.txt b/analysis/tests/src/expected/Hover.res.txt index 2753e966b..f1b882d13 100644 --- a/analysis/tests/src/expected/Hover.res.txt +++ b/analysis/tests/src/expected/Hover.res.txt @@ -87,14 +87,14 @@ Completable: Cdecorator(live) {"contents": "The `@live` decorator is for reanalyze, a static analysis tool for ReScript that can do dead code analysis.\n\n`@live` tells the dead code analysis that the value should be considered live, even though it might appear to be dead. This is typically used in case of FFI where there are indirect ways to access values. It can be added to everything that could otherwise be considered unused by the dead code analysis - values, functions, arguments, records, individual record fields, and so on.\n\n[Read more and see examples in the documentation](https://rescript-lang.org/syntax-lookup#live-decorator).\n\nHint: Did you know you can run an interactive code analysis in your project by running the command `> ReScript: Start Code Analyzer`? Try it!"} Hover src/Hover.res 115:4 -{"contents": "```rescript\n(.) => unit => int\n```"} +{"contents": "```rescript\n(. ()) => unit => int\n```"} Hover src/Hover.res 121:4 -{"contents": "```rescript\n(.) => (.) => int\n```"} +{"contents": "```rescript\n(. ()) => (. ()) => int\n```"} Hover src/Hover.res 124:4 {"contents": "```rescript\n(. unit, unit) => int\n```"} Hover src/Hover.res 127:5 -{"contents": "```rescript\n(.) => unit => int\n```"} +{"contents": "```rescript\n(. ()) => unit => int\n```"} diff --git a/analysis/tests/src/expected/RecordCompletion.res.txt b/analysis/tests/src/expected/RecordCompletion.res.txt index 2c88ceeba..710fbce2c 100644 --- a/analysis/tests/src/expected/RecordCompletion.res.txt +++ b/analysis/tests/src/expected/RecordCompletion.res.txt @@ -33,8 +33,8 @@ Completable: Cpath Value[t2].n2.n->m }] Complete src/RecordCompletion.res 19:7 -posCursor:[19:7] posNoWhite:[19:6] Found expr:[19:3->25:0] -Pexp_field [19:3->19:4] R.:[19:5->25:0] +posCursor:[19:7] posNoWhite:[19:6] Found expr:[19:3->19:7] +Pexp_field [19:3->19:4] R.:[19:5->19:7] Completable: Cpath Module[R]."" [{ "label": "name", diff --git a/analysis/vendor/res_outcome_printer/res_ast_conversion.ml b/analysis/vendor/res_outcome_printer/res_ast_conversion.ml index 20eba5ff5..39a2029e4 100644 --- a/analysis/vendor/res_outcome_printer/res_ast_conversion.ml +++ b/analysis/vendor/res_outcome_printer/res_ast_conversion.ml @@ -323,6 +323,8 @@ let hasUncurriedAttribute attrs = List.exists (fun attr -> match attr with | _ -> false ) attrs +let templateLiteralAttr = (Location.mknoloc "res.template", Parsetree.PStr []) + let normalize = let open Ast_mapper in { default_mapper with @@ -368,7 +370,7 @@ let normalize = in let s = Parsetree.Pconst_string ((escapeTemplateLiteral txt), newTag) in {p with - ppat_attributes = mapper.attributes mapper p.ppat_attributes; + ppat_attributes = templateLiteralAttr::(mapper.attributes mapper p.ppat_attributes); ppat_desc = Ppat_constant s } | _ -> @@ -396,7 +398,7 @@ let normalize = in let s = Parsetree.Pconst_string ((escapeTemplateLiteral txt), newTag) in {expr with - pexp_attributes = mapper.attributes mapper expr.pexp_attributes; + pexp_attributes= templateLiteralAttr::(mapper.attributes mapper expr.pexp_attributes); pexp_desc = Pexp_constant s } | Pexp_apply ( diff --git a/analysis/vendor/res_outcome_printer/res_ast_debugger.ml b/analysis/vendor/res_outcome_printer/res_ast_debugger.ml index 1dbb2d420..212ad1066 100644 --- a/analysis/vendor/res_outcome_printer/res_ast_debugger.ml +++ b/analysis/vendor/res_outcome_printer/res_ast_debugger.ml @@ -143,10 +143,13 @@ module SexpAst = struct string txt; optChar tag; ] - | Pconst_char c -> + | Pconst_char _ -> + Sexp.list [ + Sexp.atom "Pconst_char"; + ] + | Pconst_string(_, Some "INTERNAL_RES_CHAR_CONTENTS") -> Sexp.list [ Sexp.atom "Pconst_char"; - Sexp.atom (Char.escaped c); ] | Pconst_string (txt, tag) -> Sexp.list [ diff --git a/analysis/vendor/res_outcome_printer/res_comments_table.ml b/analysis/vendor/res_outcome_printer/res_comments_table.ml index c945bd783..b0922e175 100644 --- a/analysis/vendor/res_outcome_printer/res_comments_table.ml +++ b/analysis/vendor/res_outcome_printer/res_comments_table.ml @@ -1682,22 +1682,13 @@ and walkExprArgument (_argLabel, expr) t comments = recordRows t comments - | Ppat_or (pattern1, pattern2) -> - let (beforePattern1, insidePattern1, afterPattern1) = - partitionByLoc comments pattern1.ppat_loc - in - attach t.leading pattern1.ppat_loc beforePattern1; - walkPattern pattern1 t insidePattern1; - let (afterPattern1, rest) = - partitionAdjacentTrailing pattern1.ppat_loc afterPattern1 - in - attach t.trailing pattern1.ppat_loc afterPattern1; - let (beforePattern2, insidePattern2, afterPattern2) = - partitionByLoc rest pattern2.ppat_loc - in - attach t.leading pattern2.ppat_loc beforePattern2; - walkPattern pattern2 t insidePattern2; - attach t.trailing pattern2.ppat_loc afterPattern2 + | Ppat_or _-> + walkList + ~getLoc: (fun pattern -> pattern.Parsetree.ppat_loc) + ~walkNode: (fun pattern -> walkPattern pattern) + (Res_parsetree_viewer.collectOrPatternChain pat) + t + comments | Ppat_constraint (pattern, typ) -> let (beforePattern, insidePattern, afterPattern) = partitionByLoc comments pattern.ppat_loc diff --git a/analysis/vendor/res_outcome_printer/res_core.ml b/analysis/vendor/res_outcome_printer/res_core.ml index ea5bc14f0..799a4b538 100644 --- a/analysis/vendor/res_outcome_printer/res_core.ml +++ b/analysis/vendor/res_outcome_printer/res_core.ml @@ -136,6 +136,7 @@ let ternaryAttr = (Location.mknoloc "ns.ternary", Parsetree.PStr []) let ifLetAttr = (Location.mknoloc "ns.iflet", Parsetree.PStr []) let suppressFragileMatchWarningAttr = (Location.mknoloc "warning", Parsetree.PStr [Ast_helper.Str.eval (Ast_helper.Exp.constant (Pconst_string ("-4", None)))]) let makeBracesAttr loc = (Location.mkloc "ns.braces" loc, Parsetree.PStr []) +let templateLiteralAttr = (Location.mknoloc "res.template", Parsetree.PStr []) type stringLiteralState = | Start @@ -143,6 +144,9 @@ type stringLiteralState = | HexEscape | DecimalEscape | OctalEscape + | UnicodeEscape + | UnicodeCodePointEscape + | UnicodeEscapeStart | EscapedLineBreak type typDefOrExt = @@ -255,7 +259,7 @@ let isEs6ArrowExpression ~inTernary p = * *) false | _ -> - Parser.next state; + Parser.nextUnsafe state; (* error recovery, peek at the next token, * (elements, providerId] => { * in the example above, we have an unbalanced ] here @@ -458,11 +462,10 @@ let wrapTypeAnnotation ~loc newtypes core_type body = * e.g. foo(_, 3) becomes (__x) => foo(__x, 3) *) let processUnderscoreApplication args = - let open Parsetree in let exp_question = ref None in let hidden_var = "__x" in let check_arg ((lab, exp) as arg) = - match exp.pexp_desc with + match exp.Parsetree.pexp_desc with | Pexp_ident ({ txt = Lident "_"} as id) -> let new_id = Location.mkloc (Longident.Lident hidden_var) id.loc in let new_exp = Ast_helper.Exp.mk (Pexp_ident new_id) ~loc:exp.pexp_loc in @@ -482,15 +485,18 @@ let processUnderscoreApplication args = in (args, wrap) -let hexValue x = - match x with - | '0' .. '9' -> - (Char.code x) - 48 - | 'A' .. 'Z' -> - (Char.code x) - 55 - | 'a' .. 'z' -> - (Char.code x) - 97 - | _ -> 16 +let hexValue ch = + match ch with + | '0'..'9' -> (Char.code ch) - 48 + | 'a'..'f' -> (Char.code ch) - (Char.code 'a') + 10 + | 'A'..'F' -> (Char.code ch) + 32 - (Char.code 'a') + 10 + | _ -> 16 (* larger than any legal value *) + +(* Transform A.a into a. For use with punned record fields as in {A.a, b}. *) +let removeModuleNameFromPunnedFieldValue exp = + match exp.Parsetree.pexp_desc with + | Pexp_ident pathIdent -> {exp with pexp_desc = Pexp_ident { pathIdent with txt = Lident (Longident.last pathIdent.txt) }} + | _ -> exp let parseStringLiteral s = let len = String.length s in @@ -499,7 +505,7 @@ let parseStringLiteral s = let rec parse state i d = if i = len then (match state with - | HexEscape | DecimalEscape | OctalEscape -> false + | HexEscape | DecimalEscape | OctalEscape | UnicodeEscape | UnicodeCodePointEscape -> false | _ -> true) else let c = String.unsafe_get s i in @@ -517,6 +523,7 @@ let parseStringLiteral s = | ('\\' | ' ' | '\'' | '"') as c -> Buffer.add_char b c; parse Start (i + 1) d | 'x' -> parse HexEscape (i + 1) 0 | 'o' -> parse OctalEscape (i + 1) 0 + | 'u' -> parse UnicodeEscapeStart (i + 1) 0 | '0' .. '9' -> parse DecimalEscape i 0 | '\010' | '\013' -> parse EscapedLineBreak (i + 1) d | c -> Buffer.add_char b '\\'; Buffer.add_char b c; parse Start (i + 1) d) @@ -558,6 +565,45 @@ let parseStringLiteral s = ) else parse OctalEscape (i + 1) (d + 1) + | UnicodeEscapeStart -> + (match c with + | '{' -> parse UnicodeCodePointEscape (i + 1) 0 + | _ -> parse UnicodeEscape (i + 1) 1) + | UnicodeEscape -> + if d == 3 then + let c0 = String.unsafe_get s (i - 3) in + let c1 = String.unsafe_get s (i - 2) in + let c2 = String.unsafe_get s (i - 1) in + let c3 = String.unsafe_get s i in + let c = (4096 * (hexValue c0)) + (256 * (hexValue c1)) + (16 * (hexValue c2)) + (hexValue c3) in + if Res_utf8.isValidCodePoint c then ( + let codePoint = Res_utf8.encodeCodePoint c in + Buffer.add_string b codePoint; + parse Start (i + 1) 0 + ) else ( + false + ) + else + parse UnicodeEscape (i + 1) (d + 1) + | UnicodeCodePointEscape -> + (match c with + | '0'..'9' | 'a'..'f' | 'A'.. 'F' -> + parse UnicodeCodePointEscape (i + 1) (d + 1) + | '}' -> + let x = ref 0 in + for remaining = d downto 1 do + let ix = i - remaining in + x := (!x * 16) + (hexValue (String.unsafe_get s ix)); + done; + let c = !x in + if Res_utf8.isValidCodePoint c then ( + let codePoint = Res_utf8.encodeCodePoint !x in + Buffer.add_string b codePoint; + parse Start (i + 1) 0 + ) else ( + false + ) + | _ -> false) | EscapedLineBreak -> (match c with | ' ' | '\t' -> parse EscapedLineBreak (i + 1) d @@ -577,12 +623,13 @@ let rec parseLident p = None ) else ( let rec loop p = - if not (Recover.shouldAbortListParse p) + if not (Recover.shouldAbortListParse p) && p.token <> Eof then begin Parser.next p; loop p end in + Parser.err p (Diagnostics.lident p.Parser.token); Parser.next p; loop p; match p.Parser.token with @@ -663,21 +710,25 @@ let parseValuePath p = Longident.Ldot (path, "_") in let ident = match p.Parser.token with - | Lident ident -> Longident.Lident ident + | Lident ident -> + Parser.next p; + Longident.Lident ident | Uident ident -> Parser.next p; - if p.Parser.token = Dot then ( + let res = if p.Parser.token = Dot then ( Parser.expect Dot p; aux p (Lident ident) ) else ( Parser.err p (Diagnostics.unexpected p.Parser.token p.breadcrumbs); Longident.Lident ident - ) + ) in + if p.token <> Eof then Parser.next p; + res | token -> Parser.err p (Diagnostics.unexpected token p.breadcrumbs); + Parser.next p; Longident.Lident "_" in - Parser.next p; Location.mkloc ident (mkLoc startPos p.prevEndPos) let parseValuePathAfterDot p = @@ -891,13 +942,18 @@ let parseConstant p = let floatTxt = if isNegative then "-" ^ f else f in Parsetree.Pconst_float (floatTxt, suffix) | String s -> - let txt = if p.mode = ParseForTypeChecker then - parseStringLiteral s + if p.mode = ParseForTypeChecker then + Pconst_string (s, Some "js") else - s - in - Pconst_string(txt, None) - | Character c -> Pconst_char c + Pconst_string (s, None) + | Codepoint {c; original} -> + if p.mode = ParseForTypeChecker then + Pconst_char c + else + (* Pconst_char char does not have enough information for formatting. + * When parsing for the printer, we encode the char contents as a string + * with a special prefix. *) + Pconst_string (original, Some "INTERNAL_RES_CHAR_CONTENTS") | token -> Parser.err p (Diagnostics.unexpected token p.breadcrumbs); Pconst_string("", None) @@ -1089,7 +1145,7 @@ let rec parsePattern ?(alias=true) ?(or_=true) p = let loc = mkLoc startPos endPos in Ast_helper.Pat.construct ~loc (Location.mkloc (Longident.Lident (Token.toString token)) loc) None - | Int _ | String _ | Float _ | Character _ | Minus | Plus -> + | Int _ | String _ | Float _ | Codepoint _ | Minus | Plus -> let c = parseConstant p in begin match p.token with | DotDot -> @@ -1101,7 +1157,7 @@ let rec parsePattern ?(alias=true) ?(or_=true) p = end | Backtick -> let constant = parseTemplateConstant ~prefix:(Some "js") p in - Ast_helper.Pat.constant ~loc:(mkLoc startPos p.prevEndPos) constant + Ast_helper.Pat.constant ~attrs:[templateLiteralAttr] ~loc:(mkLoc startPos p.prevEndPos) constant | Lparen -> Parser.next p; begin match p.token with @@ -1834,7 +1890,7 @@ and parseAtomicExpr p = let loc = mkLoc startPos p.prevEndPos in Ast_helper.Exp.construct ~loc (Location.mkloc (Longident.Lident (Token.toString token)) loc) None - | Int _ | String _ | Float _ | Character _ -> + | Int _ | String _ | Float _ | Codepoint _ -> let c = parseConstant p in let loc = mkLoc startPos p.prevEndPos in Ast_helper.Exp.constant ~loc c @@ -2201,25 +2257,19 @@ and parseTemplateExpr ?(prefix="js") p = | TemplateTail txt -> Parser.next p; let loc = mkLoc startPos p.prevEndPos in - if String.length txt > 0 then - let txt = if p.mode = ParseForTypeChecker then parseTemplateStringLiteral txt else txt in - let str = Ast_helper.Exp.constant ~loc (Pconst_string(txt, Some prefix)) in - Ast_helper.Exp.apply ~loc hiddenOperator - [Nolabel, acc; Nolabel, str] - else - acc + let txt = if p.mode = ParseForTypeChecker then parseTemplateStringLiteral txt else txt in + let str = Ast_helper.Exp.constant ~attrs:[templateLiteralAttr] ~loc (Pconst_string(txt, Some prefix)) in + Ast_helper.Exp.apply ~attrs:[templateLiteralAttr] ~loc hiddenOperator + [Nolabel, acc; Nolabel, str] | TemplatePart txt -> Parser.next p; let loc = mkLoc startPos p.prevEndPos in let expr = parseExprBlock p in let fullLoc = mkLoc startPos p.prevEndPos in let txt = if p.mode = ParseForTypeChecker then parseTemplateStringLiteral txt else txt in - let str = Ast_helper.Exp.constant ~loc (Pconst_string(txt, Some prefix)) in + let str = Ast_helper.Exp.constant ~attrs:[templateLiteralAttr] ~loc (Pconst_string(txt, Some prefix)) in let next = - let a = if String.length txt > 0 then - Ast_helper.Exp.apply ~loc:fullLoc hiddenOperator [Nolabel, acc; Nolabel, str] - else acc - in + let a = Ast_helper.Exp.apply ~attrs:[templateLiteralAttr] ~loc:fullLoc hiddenOperator [Nolabel, acc; Nolabel, str] in Ast_helper.Exp.apply ~loc:fullLoc hiddenOperator [Nolabel, a; Nolabel, expr] in @@ -2234,19 +2284,16 @@ and parseTemplateExpr ?(prefix="js") p = | TemplateTail txt -> Parser.next p; let txt = if p.mode = ParseForTypeChecker then parseTemplateStringLiteral txt else txt in - Ast_helper.Exp.constant ~loc:(mkLoc startPos p.prevEndPos) (Pconst_string(txt, Some prefix)) + Ast_helper.Exp.constant ~attrs:[templateLiteralAttr] ~loc:(mkLoc startPos p.prevEndPos) (Pconst_string(txt, Some prefix)) | TemplatePart txt -> Parser.next p; let constantLoc = mkLoc startPos p.prevEndPos in let expr = parseExprBlock p in let fullLoc = mkLoc startPos p.prevEndPos in let txt = if p.mode = ParseForTypeChecker then parseTemplateStringLiteral txt else txt in - let str = Ast_helper.Exp.constant ~loc:constantLoc (Pconst_string(txt, Some prefix)) in + let str = Ast_helper.Exp.constant ~attrs:[templateLiteralAttr] ~loc:constantLoc (Pconst_string(txt, Some prefix)) in let next = - if String.length txt > 0 then - Ast_helper.Exp.apply ~loc:fullLoc hiddenOperator [Nolabel, str; Nolabel, expr] - else - expr + Ast_helper.Exp.apply ~attrs:[templateLiteralAttr] ~loc:fullLoc hiddenOperator [Nolabel, str; Nolabel, expr] in parseParts next | token -> @@ -2321,7 +2368,6 @@ and overParseConstrainedOrCoercedOrArrowExpression p expr = (Diagnostics.message msg); arrow1 | _ -> - let open Parsetree in let loc = mkLoc expr.pexp_loc.loc_start typ.ptyp_loc.loc_end in let expr = Ast_helper.Exp.constraint_ ~loc expr typ in let () = Parser.err @@ -2728,7 +2774,8 @@ and parseBracedOrRecordExpr p = Parser.expect Rbrace p; expr | _ -> - let constant = Ast_helper.Exp.constant ~loc:field.loc (Parsetree.Pconst_string(s, None)) in + let tag = if p.mode = ParseForTypeChecker then Some "js" else None in + let constant = Ast_helper.Exp.constant ~loc:field.loc (Parsetree.Pconst_string(s, tag)) in let a = parsePrimaryExpr ~operand:constant p in let e = parseBinaryExpr ~a p 1 in let e = parseTernaryExpr e p in @@ -2753,6 +2800,7 @@ and parseBracedOrRecordExpr p = end end | Uident _ | Lident _ -> + let startToken = p.token in let valueOrConstructor = parseValueOrConstructor p in begin match valueOrConstructor.pexp_desc with | Pexp_ident pathIdent -> @@ -2760,6 +2808,10 @@ and parseBracedOrRecordExpr p = begin match p.Parser.token with | Comma -> Parser.next p; + let valueOrConstructor = match startToken with + | Uident _ -> removeModuleNameFromPunnedFieldValue(valueOrConstructor) + | _ -> valueOrConstructor + in let expr = parseRecordExpr ~startPos [(pathIdent, valueOrConstructor)] p in Parser.expect Rbrace p; expr @@ -2921,6 +2973,7 @@ and parseRecordRow p = in match p.Parser.token with | Lident _ | Uident _ -> + let startToken = p.token in let field = parseValuePath p in begin match p.Parser.token with | Colon -> @@ -2928,7 +2981,12 @@ and parseRecordRow p = let fieldExpr = parseExpr p in Some (field, fieldExpr) | _ -> - Some (field, Ast_helper.Exp.ident ~loc:field.loc field) + let value = Ast_helper.Exp.ident ~loc:field.loc field in + let value = match startToken with + | Uident _ -> removeModuleNameFromPunnedFieldValue(value) + | _ -> value + in + Some (field, value) end | _ -> None @@ -4914,19 +4972,7 @@ and parsePolymorphicVariantType ~attrs p = Parser.optional p Bar |> ignore; let rowField = parseTagSpecFull p in let rowFields = parseTagSpecFulls p in - let tagNames = - if p.token == GreaterThan - then begin - Parser.next p; - let rec loop p = match p.Parser.token with - | Rbracket -> [] - | _ -> - let (ident, _loc) = parseHashIdent ~startPos:p.startPos p in - ident :: loop p - in - loop p - end - else [] in + let tagNames = parseTagNames p in let variant = let loc = mkLoc startPos p.prevEndPos in Ast_helper.Typ.variant ~attrs ~loc (rowField :: rowFields) Closed (Some tagNames) in @@ -4941,6 +4987,20 @@ and parsePolymorphicVariantType ~attrs p = Parser.expect Rbracket p; variant +and parseTagName p = + match p.Parser.token with + | Hash -> + let (ident, _loc) = parseHashIdent ~startPos:p.startPos p in + Some ident + | _ -> None + +and parseTagNames p = + if p.Parser.token == GreaterThan then + (Parser.next p; + parseRegion p ~grammar:Grammar.TagNames ~f:parseTagName) + else + [] + and parseTagSpecFulls p = match p.Parser.token with | Rbracket -> @@ -5273,11 +5333,6 @@ and parseExceptionDef ~attrs p = let loc = mkLoc startPos p.prevEndPos in Ast_helper.Te.constructor ~loc ~attrs name kind -(* module structure on the file level *) -and parseImplementation p : Parsetree.structure = - parseRegion p ~grammar:Grammar.Implementation ~f:parseStructureItemRegion - [@@progress (Parser.next, Parser.expect, Parser.checkProgress)] - and parseNewlineOrSemicolonStructure p = match p.Parser.token with | Semicolon -> @@ -5382,6 +5437,7 @@ and parseStructureItemRegion p = | _ -> None end + [@@progress (Parser.next, Parser.expect, Parser.checkProgress)] and parseJsImport ~startPos ~attrs p = Parser.expect Token.Import p; @@ -6014,11 +6070,6 @@ and parseModuleTypeOf p = let moduleExpr = parseModuleExpr p in Ast_helper.Mty.typeof_ ~loc:(mkLoc startPos p.prevEndPos) moduleExpr -(* module signature on the file level *) -and parseSpecification p = - parseRegion ~grammar:Grammar.Specification ~f:parseSignatureItemRegion p - [@@progress (Parser.next, Parser.expect, Parser.checkProgress)] - and parseNewlineOrSemicolonSignature p = match p.Parser.token with | Semicolon -> @@ -6141,6 +6192,7 @@ and parseSignatureItemRegion p = | _ -> None end + [@@progress (Parser.next, Parser.expect, Parser.checkProgress)] (* module rec module-name : module-type { and module-name: module-type } *) and parseRecModuleSpec ~attrs ~startPos p = @@ -6396,3 +6448,11 @@ and parseExtension ?(moduleLanguage=false) p = let attrId = parseAttributeId ~startPos p in let payload = parsePayload p in (attrId, payload) + +(* module signature on the file level *) +let parseSpecification p : Parsetree.signature = + parseRegion p ~grammar:Grammar.Specification ~f:parseSignatureItemRegion + +(* module structure on the file level *) +let parseImplementation p : Parsetree.structure = + parseRegion p ~grammar:Grammar.Implementation ~f:parseStructureItemRegion \ No newline at end of file diff --git a/analysis/vendor/res_outcome_printer/res_driver_reason_binary.mli b/analysis/vendor/res_outcome_printer/res_driver_reason_binary.mli index dce2d65ad..bccfc4c19 100644 --- a/analysis/vendor/res_outcome_printer/res_driver_reason_binary.mli +++ b/analysis/vendor/res_outcome_printer/res_driver_reason_binary.mli @@ -1,7 +1,3 @@ (* This module represents a general interface to parse marshalled reason ast *) -(* extracts comments and the original string data from a reason file *) -val extractConcreteSyntax : - string -> Res_token.Comment.t list * (string * Location.t) list - val parsingEngine : unit Res_driver.parsingEngine diff --git a/analysis/vendor/res_outcome_printer/res_grammar.ml b/analysis/vendor/res_outcome_printer/res_grammar.ml index 394bdd960..061c17a4d 100644 --- a/analysis/vendor/res_outcome_printer/res_grammar.ml +++ b/analysis/vendor/res_outcome_printer/res_grammar.ml @@ -58,6 +58,7 @@ type t = | JsFfiImport | Pattern | AttributePayload + | TagNames let toString = function | OpenDescription -> "an open description" @@ -118,6 +119,7 @@ let toString = function | Pattern -> "pattern" | ExprFor -> "a for expression" | AttributePayload -> "an attribute payload" + | TagNames -> "tag names" let isSignatureItemStart = function | Token.At @@ -134,7 +136,7 @@ let isSignatureItemStart = function | _ -> false let isAtomicPatternStart = function - | Token.Int _ | String _ | Character _ | Backtick + | Token.Int _ | String _ | Codepoint _ | Backtick | Lparen | Lbracket | Lbrace | Underscore | Lident _ | Uident _ | List @@ -144,7 +146,7 @@ let isAtomicPatternStart = function let isAtomicExprStart = function | Token.True | False - | Int _ | String _ | Float _ | Character _ + | Int _ | String _ | Float _ | Codepoint _ | Backtick | Uident _ | Lident _ | Hash | Lparen @@ -165,7 +167,7 @@ let isAtomicTypExprStart = function let isExprStart = function | Token.True | False - | Int _ | String _ | Float _ | Character _ | Backtick + | Int _ | String _ | Float _ | Codepoint _ | Backtick | Underscore (* _ => doThings() *) | Uident _ | Lident _ | Hash | Lparen | List | Module | Lbracket | Lbrace @@ -194,7 +196,7 @@ let isStructureItemStart = function | _ -> false let isPatternStart = function - | Token.Int _ | Float _ | String _ | Character _ | Backtick | True | False | Minus | Plus + | Token.Int _ | Float _ | String _ | Codepoint _ | Backtick | True | False | Minus | Plus | Lparen | Lbracket | Lbrace | List | Underscore | Lident _ | Uident _ | Hash @@ -301,7 +303,7 @@ let isJsxChildStart = isAtomicExprStart let isBlockExprStart = function | Token.At | Hash | Percent | Minus | MinusDot | Plus | PlusDot | Bang - | True | False | Float _ | Int _ | String _ | Character _ | Lident _ | Uident _ + | True | False | Float _ | Int _ | String _ | Codepoint _ | Lident _ | Uident _ | Lparen | List | Lbracket | Lbrace | Forwardslash | Assert | Lazy | If | For | While | Switch | Open | Module | Exception | Let | LessThan | Backtick | Try | Underscore -> true @@ -336,6 +338,7 @@ let isListElement grammar token = | JsxAttribute -> isJsxAttributeStart token | JsFfiImport -> isJsFfiImportStart token | AttributePayload -> token = Lparen + | TagNames -> token = Hash | _ -> false let isListTerminator grammar token = @@ -361,6 +364,7 @@ let isListTerminator grammar token = | PackageConstraint, token when token <> And -> true | ConstructorDeclaration, token when token <> Bar -> true | AttributePayload, Rparen -> true + | TagNames, Rbracket -> true | _ -> false diff --git a/analysis/vendor/res_outcome_printer/res_multi_printer.ml b/analysis/vendor/res_outcome_printer/res_multi_printer.ml index cfcf19427..5190f1fa8 100644 --- a/analysis/vendor/res_outcome_printer/res_multi_printer.ml +++ b/analysis/vendor/res_outcome_printer/res_multi_printer.ml @@ -1,5 +1,3 @@ -module IO = Res_io - let defaultPrintWidth = 100 (* print res files to res syntax *) @@ -51,70 +49,6 @@ let printMl ~isInterface ~filename = ~comments:parseResult.comments parseResult.parsetree -(* How does printing Reason to Res work? - * -> open a tempfile - * -> write the source code found in "filename" into the tempfile - * -> run refmt in-place in binary mode on the tempfile, - * mutates contents tempfile with marshalled AST.j - * -> read the marshalled ast (from the binary output in the tempfile) - * -> re-read the original "filename" and extract string + comment data - * -> put the comment- and string data back into the unmarshalled parsetree - * -> pretty print to res - * -> take a deep breath and exhale slowly *) -let printReason ~refmtPath ~isInterface ~filename = - (* open a tempfile *) - let (tempFilename, chan) = - (* refmt is just a prefix, `open_temp_file` takes care of providing a random name - * It tries 1000 times in the case of a name conflict. - * In practise this means that we shouldn't worry too much about filesystem races *) - Filename.open_temp_file "refmt" (if isInterface then ".rei" else ".re") in - close_out chan; - (* Write the source code found in "filename" into the tempfile *) - IO.writeFile ~filename:tempFilename ~contents:(IO.readFile ~filename); - let cmd = Printf.sprintf "%s --print=binary --in-place --interface=%b %s" refmtPath isInterface tempFilename in - (* run refmt in-place in binary mode on the tempfile *) - ignore (Sys.command cmd); - let result = - if isInterface then - let parseResult = - (* read the marshalled ast (from the binary output in the tempfile) *) - Res_driver_reason_binary.parsingEngine.parseInterface ~forPrinter:true ~filename:tempFilename in - (* re-read the original "filename" and extract string + comment data *) - let (comments, stringData) = Res_driver_reason_binary.extractConcreteSyntax filename in - (* put the comment- and string data back into the unmarshalled parsetree *) - let parseResult = { - parseResult with - parsetree = - parseResult.parsetree |> Res_ast_conversion.replaceStringLiteralSignature stringData; - comments = comments; - } in - (* pretty print to res *) - Res_printer.printInterface - ~width:defaultPrintWidth - ~comments:parseResult.comments - parseResult.parsetree - else - let parseResult = - (* read the marshalled ast (from the binary output in the tempfile) *) - Res_driver_reason_binary.parsingEngine.parseImplementation ~forPrinter:true ~filename:tempFilename in - let (comments, stringData) = Res_driver_reason_binary.extractConcreteSyntax filename in - (* put the comment- and string data back into the unmarshalled parsetree *) - let parseResult = { - parseResult with - parsetree = - parseResult.parsetree |> Res_ast_conversion.replaceStringLiteralStructure stringData; - comments = comments; - } in - (* pretty print to res *) - Res_printer.printImplementation - ~width:defaultPrintWidth - ~comments:parseResult.comments - parseResult.parsetree - in - Sys.remove tempFilename; - result -[@@raises Sys_error] - (* print the given file named input to from "language" to res, general interface exposed by the compiler *) let print language ~input = let isInterface = @@ -124,5 +58,4 @@ let print language ~input = match language with | `res -> printRes ~isInterface ~filename:input | `ml -> printMl ~isInterface ~filename:input - | `refmt path -> printReason ~refmtPath:path ~isInterface ~filename:input -[@@raises Sys_error, exit] +[@@raises exit] diff --git a/analysis/vendor/res_outcome_printer/res_multi_printer.mli b/analysis/vendor/res_outcome_printer/res_multi_printer.mli index 1a1d9624d..724f712fe 100644 --- a/analysis/vendor/res_outcome_printer/res_multi_printer.mli +++ b/analysis/vendor/res_outcome_printer/res_multi_printer.mli @@ -1,3 +1,3 @@ (* Interface to print source code from different languages to res. * Takes a filename called "input" and returns the corresponding formatted res syntax *) -val print: [`ml | `res | `refmt of string (* path to refmt *)] -> input: string -> string +val print: [`ml | `res] -> input: string -> string diff --git a/analysis/vendor/res_outcome_printer/res_outcome_printer.ml b/analysis/vendor/res_outcome_printer/res_outcome_printer.ml index 12503c115..bd2bdacd8 100644 --- a/analysis/vendor/res_outcome_printer/res_outcome_printer.ml +++ b/analysis/vendor/res_outcome_printer/res_outcome_printer.ml @@ -233,9 +233,11 @@ let printPolyVarIdent txt = ) | Otyp_alias (typ, aliasTxt) -> Doc.concat [ + Doc.lparen; printOutTypeDoc typ; Doc.text " as '"; - Doc.text aliasTxt + Doc.text aliasTxt; + Doc.rparen ] | Otyp_constr ( Oide_dot (Oide_dot (Oide_ident "Js", "Fn") , "arity0"), (* Js.Fn.arity0 *) @@ -243,7 +245,7 @@ let printPolyVarIdent txt = ) -> (* Js.Fn.arity0 -> (.) => t *) Doc.concat [ - Doc.text "(.) => "; + Doc.text "(. ()) => "; printOutTypeDoc typ; ] | Otyp_constr ( @@ -350,7 +352,7 @@ let printPolyVarIdent txt = let i = ref 0 in let package = Doc.join ~sep:Doc.line ((List.map2 [@doesNotRaise]) (fun lbl typ -> Doc.concat [ - Doc.text (if i.contents > 0 then "and " else "with "); + Doc.text (if i.contents > 0 then "and type " else "with type "); Doc.text lbl; Doc.text " = "; printOutTypeDoc typ; diff --git a/analysis/vendor/res_outcome_printer/res_parens.ml b/analysis/vendor/res_outcome_printer/res_parens.ml index 948f36925..5656a9d78 100644 --- a/analysis/vendor/res_outcome_printer/res_parens.ml +++ b/analysis/vendor/res_outcome_printer/res_parens.ml @@ -126,7 +126,8 @@ type kind = Parenthesized | Braced of Location.t | Nothing Pexp_lazy _ | Pexp_assert _ } when isLhs -> Parenthesized - | _ -> Nothing + | {Parsetree.pexp_attributes = attrs} -> + if ParsetreeViewer.hasPrintableAttributes attrs then Parenthesized else Nothing end let subBinaryExprOperand parentOperator childOperator = diff --git a/analysis/vendor/res_outcome_printer/res_parser.ml b/analysis/vendor/res_outcome_printer/res_parser.ml index 6aa63f97f..fb5e1b769 100644 --- a/analysis/vendor/res_outcome_printer/res_parser.ml +++ b/analysis/vendor/res_outcome_printer/res_parser.ml @@ -49,6 +49,7 @@ let endRegion p = * in the parser's state. Every comment contains the end position of its * previous token to facilite comment interleaving *) let rec next ?prevEndPos p = + if p.token = Eof then assert false; let prevEndPos = match prevEndPos with Some pos -> pos | None -> p.endPos in let (startPos, endPos, token) = Scanner.scan p.scanner in match token with @@ -65,6 +66,9 @@ let rec next ?prevEndPos p = p.startPos <- startPos; p.endPos <- endPos +let nextUnsafe p = + if p.token <> Eof then next p + let nextTemplateLiteralToken p = let (startPos, endPos, token) = Scanner.scanTemplateLiteralToken p.scanner in p.token <- token; @@ -82,7 +86,7 @@ let make ?(mode=ParseForTypeChecker) src filename = let parserState = { mode; scanner; - token = Token.Eof; + token = Token.Semicolon; startPos = Lexing.dummy_pos; prevEndPos = Lexing.dummy_pos; endPos = Lexing.dummy_pos; diff --git a/analysis/vendor/res_outcome_printer/res_parser.mli b/analysis/vendor/res_outcome_printer/res_parser.mli index 80a1c6394..5f215ea6b 100644 --- a/analysis/vendor/res_outcome_printer/res_parser.mli +++ b/analysis/vendor/res_outcome_printer/res_parser.mli @@ -28,6 +28,7 @@ val make: ?mode:mode -> string -> string -> t val expect: ?grammar:Grammar.t -> Token.t -> t -> unit val optional: t -> Token.t -> bool val next: ?prevEndPos:Lexing.position -> t -> unit +val nextUnsafe: t -> unit (* Does not assert on Eof, makes no progress *) val nextTemplateLiteralToken: t -> unit val lookahead: t -> (t -> 'a) -> 'a val err: diff --git a/analysis/vendor/res_outcome_printer/res_parsetree_viewer.ml b/analysis/vendor/res_outcome_printer/res_parsetree_viewer.ml index 7c25e3aaa..d310902a4 100644 --- a/analysis/vendor/res_outcome_printer/res_parsetree_viewer.ml +++ b/analysis/vendor/res_outcome_printer/res_parsetree_viewer.ml @@ -153,7 +153,7 @@ let processBracesAttr expr = let filterParsingAttrs attrs = List.filter (fun attr -> match attr with - | ({Location.txt = ("ns.ternary" | "ns.braces" | "bs" | "ns.iflet" | "ns.namedArgLoc")}, _) -> false + | ({Location.txt = ("ns.ternary" | "ns.braces" | "res.template" | "bs" | "ns.iflet" | "ns.namedArgLoc")}, _) -> false | _ -> true ) attrs @@ -292,7 +292,7 @@ let isIfLetExpr expr = match expr with let hasAttributes attrs = List.exists (fun attr -> match attr with - | ({Location.txt = "bs" | "ns.ternary" | "ns.braces" | "ns.iflet"}, _) -> false + | ({Location.txt = "bs" | "res.template" | "ns.ternary" | "ns.braces" | "ns.iflet"}, _) -> false (* Remove the fragile pattern warning for iflet expressions *) | ({Location.txt="warning"}, PStr [{ pstr_desc = Pstr_eval ({ @@ -451,17 +451,16 @@ let shouldInlineRhsBinaryExpr rhs = match rhs.pexp_desc with | Pexp_record _ -> true | _ -> false -let filterPrinteableAttributes attrs = - List.filter (fun attr -> match attr with - | ({Location.txt="bs" | "ns.ternary" | "ns.iflet" | "JSX"}, _) -> false +let isPrintableAttribute attr = + match attr with + | ({Location.txt="bs" | "res.template" | "ns.ternary" | "ns.braces" | "ns.iflet" | "JSX"}, _) -> false | _ -> true - ) attrs -let partitionPrinteableAttributes attrs = - List.partition (fun attr -> match attr with - | ({Location.txt="bs" | "ns.ternary" | "ns.iflet" | "JSX"}, _) -> false - | _ -> true - ) attrs +let hasPrintableAttributes attrs = List.exists isPrintableAttribute attrs + +let filterPrintableAttributes attrs = List.filter isPrintableAttribute attrs + +let partitionPrintableAttributes attrs = List.partition isPrintableAttribute attrs let requiresSpecialCallbackPrintingLastArg args = let rec loop args = match args with @@ -511,17 +510,19 @@ let rec collectPatternsFromListConstruct acc pattern = collectPatternsFromListConstruct (pat::acc) rest | _ -> List.rev acc, pattern -(* Simple heuristic to detect template literal sugar: - * `${user.name} lastName` parses internally as user.name ++ ` lastName`. - * The thing is: the ++ operator (parsed as `^`) will always have a ghost loc. - * A ghost loc is only produced by our parser. - * Hence, if we have that ghost operator, we know for sure it's a template literal. *) + +let hasTemplateLiteralAttr attrs = List.exists (fun attr -> match attr with +| ({Location.txt = "res.template"}, _) -> true +| _ -> false) attrs + let isTemplateLiteral expr = match expr.pexp_desc with | Pexp_apply ( - {pexp_desc = Pexp_ident {txt = Longident.Lident "^"; loc}}, + {pexp_desc = Pexp_ident {txt = Longident.Lident "^"}}, [Nolabel, _; Nolabel, _] - ) when loc.loc_ghost -> true + ) when hasTemplateLiteralAttr expr.pexp_attributes -> true + | Pexp_constant (Pconst_string (_, Some "")) -> true + | Pexp_constant _ when hasTemplateLiteralAttr expr.pexp_attributes -> true | _ -> false (* Blue | Red | Green -> [Blue; Red; Green] *) diff --git a/analysis/vendor/res_outcome_printer/res_parsetree_viewer.mli b/analysis/vendor/res_outcome_printer/res_parsetree_viewer.mli index f83ea02f4..c196d8b1c 100644 --- a/analysis/vendor/res_outcome_printer/res_parsetree_viewer.mli +++ b/analysis/vendor/res_outcome_printer/res_parsetree_viewer.mli @@ -81,8 +81,9 @@ val hasJsxAttribute: Parsetree.attributes -> bool val shouldIndentBinaryExpr: Parsetree.expression -> bool val shouldInlineRhsBinaryExpr: Parsetree.expression -> bool -val filterPrinteableAttributes: Parsetree.attributes -> Parsetree.attributes -val partitionPrinteableAttributes: Parsetree.attributes -> (Parsetree.attributes * Parsetree.attributes) +val hasPrintableAttributes: Parsetree.attributes -> bool +val filterPrintableAttributes: Parsetree.attributes -> Parsetree.attributes +val partitionPrintableAttributes: Parsetree.attributes -> (Parsetree.attributes * Parsetree.attributes) val requiresSpecialCallbackPrintingLastArg: (Asttypes.arg_label * Parsetree.expression) list -> bool val requiresSpecialCallbackPrintingFirstArg: (Asttypes.arg_label * Parsetree.expression) list -> bool @@ -109,6 +110,7 @@ val collectPatternsFromListConstruct: val isBlockExpr : Parsetree.expression -> bool val isTemplateLiteral: Parsetree.expression -> bool +val hasTemplateLiteralAttr: Parsetree.attributes -> bool val collectOrPatternChain: Parsetree.pattern -> Parsetree.pattern list diff --git a/analysis/vendor/res_outcome_printer/res_printer.ml b/analysis/vendor/res_outcome_printer/res_printer.ml index edd92d326..9c1facf29 100644 --- a/analysis/vendor/res_outcome_printer/res_printer.ml +++ b/analysis/vendor/res_outcome_printer/res_printer.ml @@ -459,19 +459,38 @@ let printPolyVarIdent txt = Doc.text txt; Doc.text"\"" ] - | NormalIdent -> Doc.text txt + | NormalIdent -> match txt with + | "" -> Doc.concat [ + Doc.text "\""; + Doc.text txt; + Doc.text"\"" + ] + | _ -> Doc.text txt -let printLident l = match l with +let printLident l = + let flatLidOpt lid = + let rec flat accu = function + | Longident.Lident s -> Some (s :: accu) + | Ldot (lid, s) -> flat (s :: accu) lid + | Lapply (_, _) -> None + in + flat [] lid + in + match l with | Longident.Lident txt -> printIdentLike txt | Longident.Ldot (path, txt) -> - let txts = Longident.flatten path in - Doc.concat [ - Doc.join ~sep:Doc.dot (List.map Doc.text txts); - Doc.dot; - printIdentLike txt; - ] - | _ -> Doc.text("printLident: Longident.Lapply is not supported") + let doc = match flatLidOpt path with + | Some txts -> + Doc.concat [ + Doc.join ~sep:Doc.dot (List.map Doc.text txts); + Doc.dot; + printIdentLike txt; + ] + | None -> Doc.text("printLident: Longident.Lapply is not supported") + in + doc + | Lapply (_, _) -> Doc.text("printLident: Longident.Lapply is not supported") let printLongidentLocation l cmtTbl = let doc = printLongident l.Location.txt in @@ -495,7 +514,7 @@ let printStringContents txt = let lines = String.split_on_char '\n' txt in Doc.join ~sep:Doc.literalLine (List.map Doc.text lines) -let printConstant c = match c with +let printConstant ?(templateLiteral=false) c = match c with | Parsetree.Pconst_integer (s, suffix) -> begin match suffix with | Some c -> Doc.text (s ^ (Char.escaped c)) @@ -508,14 +527,35 @@ let printConstant c = match c with Doc.text "\""; ] | Pconst_string (txt, Some prefix) -> - Doc.concat [ - if prefix = "js" then Doc.nil else Doc.text prefix; - Doc.text "`"; - printStringContents txt; - Doc.text "`"; - ] + if prefix = "INTERNAL_RES_CHAR_CONTENTS" then + Doc.concat [Doc.text "'"; Doc.text txt; Doc.text "'"] + else + let (lquote, rquote) = + if templateLiteral then ("`", "`") else ("\"", "\"") + in + Doc.concat [ + if prefix = "js" then Doc.nil else Doc.text prefix; + Doc.text lquote; + printStringContents txt; + Doc.text rquote; + ] | Pconst_float (s, _) -> Doc.text s - | Pconst_char c -> Doc.text ("'" ^ (Char.escaped c) ^ "'") + | Pconst_char c -> + let str = match c with + | '\'' -> "\\'" + | '\\' -> "\\\\" + | '\n' -> "\\n" + | '\t' -> "\\t" + | '\r' -> "\\r" + | '\b' -> "\\b" + | ' ' .. '~' as c -> + let s = (Bytes.create [@doesNotRaise]) 1 in + Bytes.unsafe_set s 0 c; + Bytes.unsafe_to_string s + | c -> + Res_utf8.encodeCodePoint (Obj.magic c) + in + Doc.text ("'" ^ str ^ "'") let rec printStructure (s : Parsetree.structure) t = match s with @@ -760,7 +800,7 @@ and printModType modType cmtTbl = {lbl.Asttypes.loc with loc_end = modType.Parsetree.pmty_loc.loc_end} in let attrs = printAttributes attrs cmtTbl in - let lblDoc = if lbl.Location.txt = "_" then Doc.nil + let lblDoc = if lbl.Location.txt = "_" || lbl.txt = "*" then Doc.nil else let doc = Doc.text lbl.txt in printComments doc cmtTbl lbl.loc @@ -2079,7 +2119,9 @@ and printPattern (p : Parsetree.pattern) cmtTbl = let patternWithoutAttributes = match p.ppat_desc with | Ppat_any -> Doc.text "_" | Ppat_var var -> printIdentLike var.txt - | Ppat_constant c -> printConstant c + | Ppat_constant c -> + let templateLiteral = ParsetreeViewer.hasTemplateLiteralAttr p.ppat_attributes in + printConstant ~templateLiteral c | Ppat_tuple patterns -> Doc.group( Doc.concat([ @@ -2522,7 +2564,8 @@ and printIfChain pexp_attributes ifs elseExpr cmtTbl = and printExpression (e : Parsetree.expression) cmtTbl = let printedExpression = match e.pexp_desc with - | Parsetree.Pexp_constant c -> printConstant c + | Parsetree.Pexp_constant c -> + printConstant ~templateLiteral:(ParsetreeViewer.isTemplateLiteral e) c | Pexp_construct _ when ParsetreeViewer.hasJsxAttribute e.pexp_attributes -> printJsxFragment e cmtTbl | Pexp_construct ({txt = Longident.Lident "()"}, _) -> Doc.text "()" @@ -2777,6 +2820,10 @@ and printExpression (e : Parsetree.expression) cmtTbl = let forceBreak = e.pexp_loc.loc_start.pos_lnum < e.pexp_loc.loc_end.pos_lnum in + let punningAllowed = match spreadExpr, rows with + | (None, [_]) -> false (* disallow punning for single-element records *) + | _ -> true + in Doc.breakableGroup ~forceBreak ( Doc.concat([ Doc.lbrace; @@ -2785,7 +2832,7 @@ and printExpression (e : Parsetree.expression) cmtTbl = Doc.softLine; spread; Doc.join ~sep:(Doc.concat [Doc.text ","; Doc.line]) - (List.map (fun row -> printRecordRow row cmtTbl) rows) + (List.map (fun row -> printRecordRow row cmtTbl punningAllowed) rows) ] ); Doc.trailingComma; @@ -3445,7 +3492,7 @@ and printBinaryExpression (expr : Parsetree.expression) cmtTbl = let leftPrinted = flatten ~isLhs:true left operator in let rightPrinted = let (_, rightAttrs) = - ParsetreeViewer.partitionPrinteableAttributes right.pexp_attributes + ParsetreeViewer.partitionPrintableAttributes right.pexp_attributes in let doc = printExpressionWithComments @@ -3457,10 +3504,13 @@ and printBinaryExpression (expr : Parsetree.expression) cmtTbl = else doc in - let printeableAttrs = - ParsetreeViewer.filterPrinteableAttributes right.pexp_attributes + let printableAttrs = + ParsetreeViewer.filterPrintableAttributes right.pexp_attributes in - Doc.concat [printAttributes printeableAttrs cmtTbl; doc] + let doc = Doc.concat [printAttributes printableAttrs cmtTbl; doc] in + match printableAttrs with + | [] -> doc + | _ -> addParens doc in let doc = Doc.concat [ leftPrinted; @@ -4397,12 +4447,7 @@ and printCases (cases: Parsetree.case list) cmtTbl = Doc.concat [ Doc.line; printList - ~getLoc:(fun n -> {n.Parsetree.pc_lhs.ppat_loc with - loc_end = - match ParsetreeViewer.processBracesAttr n.Parsetree.pc_rhs with - | (None, _) -> n.pc_rhs.pexp_loc.loc_end - | (Some ({loc}, _), _) -> loc.Location.loc_end - }) + ~getLoc:(fun n -> {n.Parsetree.pc_lhs.ppat_loc with loc_end = n.pc_rhs.pexp_loc.loc_end}) ~print:printCase ~nodes:cases cmtTbl @@ -4788,17 +4833,28 @@ and printDirectionFlag flag = match flag with | Asttypes.Downto -> Doc.text " downto " | Asttypes.Upto -> Doc.text " to " -and printRecordRow (lbl, expr) cmtTbl = +and printRecordRow (lbl, expr) cmtTbl punningAllowed = let cmtLoc = {lbl.loc with loc_end = expr.pexp_loc.loc_end} in - let doc = Doc.group (Doc.concat [ - printLidentPath lbl cmtTbl; - Doc.text ": "; - (let doc = printExpressionWithComments expr cmtTbl in - match Parens.expr expr with - | Parens.Parenthesized -> addParens doc - | Braced braces -> printBraces doc expr braces - | Nothing -> doc); - ]) in + let doc = Doc.group ( + match expr.pexp_desc with + | Pexp_ident({txt = Lident key; loc = keyLoc}) when ( + punningAllowed && + Longident.last lbl.txt = key && + lbl.loc.loc_start.pos_cnum == keyLoc.loc_start.pos_cnum + ) -> + (* print punned field *) + printLidentPath lbl cmtTbl; + | _ -> + Doc.concat [ + printLidentPath lbl cmtTbl; + Doc.text ": "; + (let doc = printExpressionWithComments expr cmtTbl in + match Parens.expr expr with + | Parens.Parenthesized -> addParens doc + | Braced braces -> printBraces doc expr braces + | Nothing -> doc); + ] + ) in printComments doc cmtTbl cmtLoc and printBsObjectRow (lbl, expr) cmtTbl = diff --git a/analysis/vendor/res_outcome_printer/res_scanner.ml b/analysis/vendor/res_outcome_printer/res_scanner.ml index 9491e2089..d35f2f060 100644 --- a/analysis/vendor/res_outcome_printer/res_scanner.ml +++ b/analysis/vendor/res_outcome_printer/res_scanner.ml @@ -319,9 +319,9 @@ let scanStringEscapeSequence ~startPos scanner = loop (n - 1) (x * base + d) in let x = loop n 0 in - if x > max then + if x > max || 0xD800 <= x && x < 0xE000 then let pos = position scanner in - let msg = "invalid escape sequence (value too high)" in + let msg = "escape sequence is invalid unicode code point" in scanner.err ~startPos ~endPos:pos (Diagnostics.message msg) in match scanner.ch with @@ -339,6 +339,24 @@ let scanStringEscapeSequence ~startPos scanner = (* hex *) next scanner; scan ~n:2 ~base:16 ~max:255 + | 'u' -> + next scanner; + (match scanner.ch with + | '{' -> + (* unicode code point escape sequence: '\u{7A}', one or more hex digits *) + next scanner; + let x = ref 0 in + while match scanner.ch with | '0'..'9' | 'a'..'f' | 'A'..'F' -> true | _ -> false do + x := (!x * 16) + (digitValue scanner.ch); + next scanner + done; + (* consume '}' in '\u{7A}' *) + (match scanner.ch with + | '}' -> next scanner + | _ -> ()); + | _ -> + scan ~n:4 ~base:16 ~max:Res_utf8.max + ) | _ -> (* unknown escape sequence * TODO: we should warn the user here. Let's not make it a hard error for now, for reason compat *) @@ -381,6 +399,8 @@ let scanString scanner = Token.String (scan ()) let scanEscape scanner = + (* '\' consumed *) + let offset = scanner.offset - 1 in let convertNumber scanner ~n ~base = let x = ref 0 in for _ = n downto 1 do @@ -388,10 +408,13 @@ let scanEscape scanner = x := (!x * base) + d; next scanner done; - (Char.chr [@doesNotRaise]) !x + let c = !x in + if Res_utf8.isValidCodePoint c then + Char.unsafe_chr c + else + Char.unsafe_chr Res_utf8.repl in - (* let offset = scanner.offset in *) - let c = match scanner.ch with + let codepoint = match scanner.ch with | '0'..'9' -> convertNumber scanner ~n:3 ~base:10 | 'b' -> next scanner; '\008' | 'n' -> next scanner; '\010' @@ -399,11 +422,36 @@ let scanEscape scanner = | 't' -> next scanner; '\009' | 'x' -> next scanner; convertNumber scanner ~n:2 ~base:16 | 'o' -> next scanner; convertNumber scanner ~n:3 ~base:8 + | 'u' -> + next scanner; + begin match scanner.ch with + | '{' -> + (* unicode code point escape sequence: '\u{7A}', one or more hex digits *) + next scanner; + let x = ref 0 in + while match scanner.ch with | '0'..'9' | 'a'..'f' | 'A'..'F' -> true | _ -> false do + x := (!x * 16) + (digitValue scanner.ch); + next scanner + done; + (* consume '}' in '\u{7A}' *) + (match scanner.ch with + | '}' -> next scanner + | _ -> ()); + let c = !x in + if Res_utf8.isValidCodePoint c then + Char.unsafe_chr c + else + Char.unsafe_chr Res_utf8.repl + | _ -> + (* unicode escape sequence: '\u007A', exactly 4 hex digits *) + convertNumber scanner ~n:4 ~base:16 + end | ch -> next scanner; ch in + let contents = (String.sub [@doesNotRaise]) scanner.src offset (scanner.offset - offset) in next scanner; (* Consume \' *) (* TODO: do we know it's \' ? *) - Token.Character c + Token.Codepoint {c = codepoint; original = contents} let scanSingleLineComment scanner = let startOff = scanner.offset in @@ -615,8 +663,26 @@ let rec scan scanner = then relying on matching on the quote *) next scanner; SingleQuote | '\\', _ -> next2 scanner; scanEscape scanner - | ch, '\'' -> next3 scanner; Token.Character ch - | _ -> next scanner; SingleQuote) + | ch, '\'' -> + let offset = scanner.offset + 1 in + next3 scanner; + Token.Codepoint {c = ch; original = (String.sub [@doesNotRaise]) scanner.src offset 1} + | ch, _ -> + next scanner; + let offset = scanner.offset in + let (codepoint, length) = Res_utf8.decodeCodePoint scanner.offset scanner.src (String.length scanner.src) in + for _ = 0 to length - 1 do + next scanner + done; + if scanner.ch = '\'' then ( + let contents = (String.sub [@doesNotRaise]) scanner.src offset length in + next scanner; + Token.Codepoint {c = Obj.magic codepoint; original = contents} + ) else ( + scanner.ch <- ch; + scanner.offset <- offset; + SingleQuote + )) | '!' -> (match peek scanner, peek2 scanner with | '=', '=' -> next3 scanner; Token.BangEqualEqual diff --git a/analysis/vendor/res_outcome_printer/res_token.ml b/analysis/vendor/res_outcome_printer/res_token.ml index a14491dd6..b901276ab 100644 --- a/analysis/vendor/res_outcome_printer/res_token.ml +++ b/analysis/vendor/res_outcome_printer/res_token.ml @@ -3,7 +3,7 @@ module Comment = Res_comment type t = | Open | True | False - | Character of char + | Codepoint of {c: char; original: string} | Int of {i: string; suffix: char option} | Float of {f: string; suffix: char option} | String of string @@ -88,7 +88,7 @@ let precedence = function let toString = function | Open -> "open" | True -> "true" | False -> "false" - | Character c -> "character '" ^ (Char.escaped c) ^ "'" + | Codepoint {original} -> "codepoint '" ^ original ^ "'" | String s -> "string \"" ^ s ^ "\"" | Lident str -> str | Uident str -> str diff --git a/analysis/vendor/res_outcome_printer/res_utf8.ml b/analysis/vendor/res_outcome_printer/res_utf8.ml new file mode 100644 index 000000000..a8fd99ee0 --- /dev/null +++ b/analysis/vendor/res_outcome_printer/res_utf8.ml @@ -0,0 +1,141 @@ +(* https://tools.ietf.org/html/rfc3629#section-10 *) +(* let bom = 0xFEFF *) + +let repl = 0xFFFD + +(* let min = 0x0000 *) +let max = 0x10FFFF + +let surrogateMin = 0xD800 +let surrogateMax = 0xDFFF + +(* + * Char. number range | UTF-8 octet sequence + * (hexadecimal) | (binary) + * --------------------+--------------------------------------------- + * 0000 0000-0000 007F | 0xxxxxxx + * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx + * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx + * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + *) +let h2 = 0b1100_0000 +let h3 = 0b1110_0000 +let h4 = 0b1111_0000 + +let cont_mask = 0b0011_1111 + +type category = { + low: int; + high: int; + size: int; +} + +let locb = 0b1000_0000 +let hicb = 0b1011_1111 + +let categoryTable = [| +(* 0 *) {low = -1; high= -1; size= 1}; (* invalid *) +(* 1 *) {low = 1; high= -1; size= 1}; (* ascii *) +(* 2 *) {low = locb; high= hicb; size= 2}; +(* 3 *) {low = 0xA0; high= hicb; size= 3}; +(* 4 *) {low = locb; high= hicb; size= 3}; +(* 5 *) {low = locb; high= 0x9F; size= 3}; +(* 6 *) {low = 0x90; high= hicb; size= 4}; +(* 7 *) {low = locb; high= hicb; size= 4}; +(* 8 *) {low = locb; high= 0x8F; size= 4}; + +|] + +let categories = [| + 1; 1; 1; 1; 1; 1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1; + 1; 1; 1; 1; 1; 1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1; + 1; 1; 1; 1; 1; 1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1; + 1; 1; 1; 1; 1; 1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1; + 1; 1; 1; 1; 1; 1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1; + 1; 1; 1; 1; 1; 1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1; + 1; 1; 1; 1; 1; 1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1; + 1; 1; 1; 1; 1; 1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1 ;1; + + 0; 0; 0; 0;0; 0; 0; 0;0; 0; 0; 0;0; 0; 0; 0; + 0; 0; 0; 0;0; 0; 0; 0;0; 0; 0; 0;0; 0; 0; 0; + 0; 0; 0; 0;0; 0; 0; 0;0; 0; 0; 0;0; 0; 0; 0; + 0; 0; 0; 0;0; 0; 0; 0;0; 0; 0; 0;0; 0; 0; 0; + (* surrogate range U+D800 - U+DFFFF = 55296 - 917503 *) + 0; 0; 2; 2;2; 2; 2; 2;2; 2; 2; 2;2; 2; 2; 2; + 2; 2; 2; 2; 2; 2; 2; 2; 2; 2; 2; 2; 2; 2; 2; 2; + 3; 4; 4; 4; 4; 4; 4; 4; 4; 4; 4; 4; 4; 5; 4; 4; + 6; 7; 7 ;7; 8; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; 0; +|] + +let decodeCodePoint i s len = + if len < 1 then (repl, 1) else + let first = int_of_char (String.unsafe_get s i) in + if first < 128 then (first, 1) else + let index = Array.unsafe_get categories first in + if index = 0 then + (repl, 1) + else + let cat = Array.unsafe_get categoryTable index in + if len < i + cat.size then + (repl, 1) + else if cat.size == 2 then + let c1 = int_of_char (String.unsafe_get s (i + 1)) in + if c1 < cat.low || cat.high < c1 then + (repl, 1) + else + let i1 = c1 land 0b00111111 in + let i0 = (first land 0b00011111) lsl 6 in + let uc = i0 lor i1 in + (uc, 2) + else if cat.size == 3 then + let c1 = int_of_char (String.unsafe_get s (i + 1)) in + let c2 = int_of_char (String.unsafe_get s (i + 2)) in + if c1 < cat.low || cat.high < c1 || c2 < locb || hicb < c2 then (repl, 1) + else + let i0 = (first land 0b00001111) lsl 12 in + let i1 = (c1 land 0b00111111) lsl 6 in + let i2 = (c2 land 0b00111111) in + let uc = i0 lor i1 lor i2 in + (uc, 3) + else + let c1 = int_of_char (String.unsafe_get s (i +1)) in + let c2 = int_of_char (String.unsafe_get s (i +2)) in + let c3 = int_of_char (String.unsafe_get s (i +3)) in + if c1 < cat.low || cat.high < c1 || + c2 < locb || hicb < c2 || c3 < locb || hicb < c3 + then (repl, 1) + else + let i1 = (c1 land 0x3f) lsl 12 in + let i2 = (c2 land 0x3f) lsl 6 in + let i3 = (c3 land 0x3f) in + let i0 = (first land 0x07) lsl 18 in + let uc = i0 lor i3 lor i2 lor i1 in + (uc, 4) + +let encodeCodePoint c = + if c <= 127 then ( + let bytes = (Bytes.create [@doesNotRaise]) 1 in + Bytes.unsafe_set bytes 0 (Char.unsafe_chr c); + Bytes.unsafe_to_string bytes + ) else if c <= 2047 then ( + let bytes = (Bytes.create [@doesNotRaise]) 2 in + Bytes.unsafe_set bytes 0 (Char.unsafe_chr (h2 lor (c lsr 6))); + Bytes.unsafe_set bytes 1 (Char.unsafe_chr (0b1000_0000 lor (c land cont_mask))); + Bytes.unsafe_to_string bytes + ) else if c <= 65535 then ( + let bytes = (Bytes.create [@doesNotRaise]) 3 in + Bytes.unsafe_set bytes 0 (Char.unsafe_chr (h3 lor (c lsr 12))); + Bytes.unsafe_set bytes 1 (Char.unsafe_chr (0b1000_0000 lor ((c lsr 6) land cont_mask))); + Bytes.unsafe_set bytes 2 (Char.unsafe_chr (0b1000_0000 lor (c land cont_mask))); + Bytes.unsafe_to_string bytes + ) else (* if c <= max then *) ( + let bytes = (Bytes.create [@doesNotRaise]) 4 in + Bytes.unsafe_set bytes 0 (Char.unsafe_chr (h4 lor (c lsr 18))); + Bytes.unsafe_set bytes 1 (Char.unsafe_chr (0b1000_0000 lor ((c lsr 12) land cont_mask))); + Bytes.unsafe_set bytes 2 (Char.unsafe_chr (0b1000_0000 lor ((c lsr 6) land cont_mask))); + Bytes.unsafe_set bytes 3 (Char.unsafe_chr (0b1000_0000 lor (c land cont_mask))); + Bytes.unsafe_to_string bytes + ) + +let isValidCodePoint c = + 0 <= c && c < surrogateMin || surrogateMax < c && c <= max diff --git a/analysis/vendor/res_outcome_printer/res_utf8.mli b/analysis/vendor/res_outcome_printer/res_utf8.mli new file mode 100644 index 000000000..4b7462a4e --- /dev/null +++ b/analysis/vendor/res_outcome_printer/res_utf8.mli @@ -0,0 +1,9 @@ +val repl: int + +val max: int + +val decodeCodePoint: int -> string -> int -> int * int + +val encodeCodePoint: int -> string + +val isValidCodePoint: int -> bool