From 7d4edafdaf2ad546a48fde6a36097ad00ae64b57 Mon Sep 17 00:00:00 2001 From: Tomer Aberbach Date: Sat, 26 Sep 2026 13:14:21 -0400 Subject: [PATCH] Tokenize type on the line after `as` or `satisfies` When a line ends with `as` or `satisfies`, the type on the next line receives expression scopes. In `b as\n A | undefined`, `A` is a variable, `|` a bitwise operator, and `undefined` a constant. The `as`/`satisfies` rule ends the type at `$` and `^`, so the type ends at the line break right after the keyword. Prettier emits this layout when an `as` expression exceeds the print width. A separate rule now matches `as` or `satisfies` at the end of a line, modeled on the rule for a return type that starts on the next line. It ends the type at the end of the first non-empty line that lacks a trailing `|` or `&`, instead of at the line break after the keyword. A union written with a leading `|` on each line still ends after its first member, because the end pattern cannot match past the current line to check whether the next line continues the type. Multi-line return types have the same limitation. Fixes #1079 --- TypeScript.YAML-tmLanguage | 8 ++ TypeScript.tmLanguage | 26 ++++ TypeScriptReact.tmLanguage | 26 ++++ tests/baselines/Issue1079.baseline.txt | 184 +++++++++++++++++++++++++ tests/cases/Issue1079.ts | 12 ++ 5 files changed, 256 insertions(+) create mode 100644 tests/baselines/Issue1079.baseline.txt create mode 100644 tests/cases/Issue1079.ts diff --git a/TypeScript.YAML-tmLanguage b/TypeScript.YAML-tmLanguage index 36fa5547..778b27fc 100644 --- a/TypeScript.YAML-tmLanguage +++ b/TypeScript.YAML-tmLanguage @@ -1752,6 +1752,14 @@ repository: captures: '1': { name: keyword.control.as.ts } '2': { name: storage.modifier.ts } + # if type starts on next line use end of a non-empty line as end of type + - begin: '{{startOfIdentifier}}(?:(as)|(satisfies))(?=\s*$)' + beginCaptures: + '1': { name: keyword.control.as.ts } + '2': { name: keyword.control.satisfies.ts } + end: (?]|\|\||\&\&|\!\=\=|^\s*$|({{startOfIdentifier}}(as|satisfies)\s+)|(\s+\<))|((?<=\S)(?=\s*$))) + patterns: + - include: '#type' - begin: '{{startOfIdentifier}}(?:(as)|(satisfies))\s+' beginCaptures: '1': { name: keyword.control.as.ts } diff --git a/TypeScript.tmLanguage b/TypeScript.tmLanguage index 4d91f6ad..440ed1e1 100644 --- a/TypeScript.tmLanguage +++ b/TypeScript.tmLanguage @@ -5696,6 +5696,32 @@ + + begin + (?<![_$[:alnum:]])(?:(?<=\.\.\.)|(?<!\.))(?:(as)|(satisfies))(?=\s*$) + beginCaptures + + 1 + + name + keyword.control.as.ts + + 2 + + name + keyword.control.satisfies.ts + + + end + (?<!\bas|\bsatisfies|[|&])((?=[;),}\]:?\-\+\>]|\|\||\&\&|\!\=\=|^\s*$|((?<![_$[:alnum:]])(?:(?<=\.\.\.)|(?<!\.))(as|satisfies)\s+)|(\s+\<))|((?<=\S)(?=\s*$))) + patterns + + + include + #type + + + begin (?<![_$[:alnum:]])(?:(?<=\.\.\.)|(?<!\.))(?:(as)|(satisfies))\s+ diff --git a/TypeScriptReact.tmLanguage b/TypeScriptReact.tmLanguage index a7aec5da..a8d93de3 100644 --- a/TypeScriptReact.tmLanguage +++ b/TypeScriptReact.tmLanguage @@ -5642,6 +5642,32 @@ + + begin + (?<![_$[:alnum:]])(?:(?<=\.\.\.)|(?<!\.))(?:(as)|(satisfies))(?=\s*$) + beginCaptures + + 1 + + name + keyword.control.as.tsx + + 2 + + name + keyword.control.satisfies.tsx + + + end + (?<!\bas|\bsatisfies|[|&])((?=[;),}\]:?\-\+\>]|\|\||\&\&|\!\=\=|^\s*$|((?<![_$[:alnum:]])(?:(?<=\.\.\.)|(?<!\.))(as|satisfies)\s+)|(\s+\<))|((?<=\S)(?=\s*$))) + patterns + + + include + #type + + + begin (?<![_$[:alnum:]])(?:(?<=\.\.\.)|(?<!\.))(?:(as)|(satisfies))\s+ diff --git a/tests/baselines/Issue1079.baseline.txt b/tests/baselines/Issue1079.baseline.txt new file mode 100644 index 00000000..c91e3604 --- /dev/null +++ b/tests/baselines/Issue1079.baseline.txt @@ -0,0 +1,184 @@ +original file +----------------------------------- +const a = b as + A | undefined + +const a = b satisfies + A | undefined + +const inlineSourceMap = convertSourceMap.fromSource(content)?.toObject() as + SourceMap | undefined +foo(inlineSourceMap) + +const e = (f as + G); + +----------------------------------- + +Grammar: TypeScript.tmLanguage +----------------------------------- +>const a = b as + ^^^^^ + source.ts meta.var.expr.ts storage.type.ts + ^ + source.ts meta.var.expr.ts + ^ + source.ts meta.var.expr.ts meta.var-single-variable.expr.ts meta.definition.variable.ts variable.other.constant.ts + ^ + source.ts meta.var.expr.ts meta.var-single-variable.expr.ts + ^ + source.ts meta.var.expr.ts keyword.operator.assignment.ts + ^ + source.ts meta.var.expr.ts + ^ + source.ts meta.var.expr.ts variable.other.readwrite.ts + ^ + source.ts meta.var.expr.ts + ^^ + source.ts meta.var.expr.ts keyword.control.as.ts +> A | undefined + ^^ + source.ts meta.var.expr.ts + ^ + source.ts meta.var.expr.ts entity.name.type.ts + ^ + source.ts meta.var.expr.ts + ^ + source.ts meta.var.expr.ts keyword.operator.type.ts + ^ + source.ts meta.var.expr.ts + ^^^^^^^^^ + source.ts meta.var.expr.ts support.type.builtin.ts +> + ^ + source.ts +>const a = b satisfies + ^^^^^ + source.ts meta.var.expr.ts storage.type.ts + ^ + source.ts meta.var.expr.ts + ^ + source.ts meta.var.expr.ts meta.var-single-variable.expr.ts meta.definition.variable.ts variable.other.constant.ts + ^ + source.ts meta.var.expr.ts meta.var-single-variable.expr.ts + ^ + source.ts meta.var.expr.ts keyword.operator.assignment.ts + ^ + source.ts meta.var.expr.ts + ^ + source.ts meta.var.expr.ts variable.other.readwrite.ts + ^ + source.ts meta.var.expr.ts + ^^^^^^^^^ + source.ts meta.var.expr.ts keyword.control.satisfies.ts +> A | undefined + ^^ + source.ts meta.var.expr.ts + ^ + source.ts meta.var.expr.ts entity.name.type.ts + ^ + source.ts meta.var.expr.ts + ^ + source.ts meta.var.expr.ts keyword.operator.type.ts + ^ + source.ts meta.var.expr.ts + ^^^^^^^^^ + source.ts meta.var.expr.ts support.type.builtin.ts +> + ^ + source.ts +>const inlineSourceMap = convertSourceMap.fromSource(content)?.toObject() as + ^^^^^ + source.ts meta.var.expr.ts storage.type.ts + ^ + source.ts meta.var.expr.ts + ^^^^^^^^^^^^^^^ + source.ts meta.var.expr.ts meta.var-single-variable.expr.ts meta.definition.variable.ts variable.other.constant.ts + ^ + source.ts meta.var.expr.ts meta.var-single-variable.expr.ts + ^ + source.ts meta.var.expr.ts keyword.operator.assignment.ts + ^ + source.ts meta.var.expr.ts + ^^^^^^^^^^^^^^^^ + source.ts meta.var.expr.ts meta.function-call.ts variable.other.object.ts + ^ + source.ts meta.var.expr.ts meta.function-call.ts punctuation.accessor.ts + ^^^^^^^^^^ + source.ts meta.var.expr.ts meta.function-call.ts entity.name.function.ts + ^ + source.ts meta.var.expr.ts meta.brace.round.ts + ^^^^^^^ + source.ts meta.var.expr.ts variable.other.readwrite.ts + ^ + source.ts meta.var.expr.ts meta.brace.round.ts + ^^ + source.ts meta.var.expr.ts meta.function-call.ts punctuation.accessor.optional.ts + ^^^^^^^^ + source.ts meta.var.expr.ts meta.function-call.ts entity.name.function.ts + ^ + source.ts meta.var.expr.ts meta.brace.round.ts + ^ + source.ts meta.var.expr.ts meta.brace.round.ts + ^ + source.ts meta.var.expr.ts + ^^ + source.ts meta.var.expr.ts keyword.control.as.ts +> SourceMap | undefined + ^^ + source.ts meta.var.expr.ts + ^^^^^^^^^ + source.ts meta.var.expr.ts entity.name.type.ts + ^ + source.ts meta.var.expr.ts + ^ + source.ts meta.var.expr.ts keyword.operator.type.ts + ^ + source.ts meta.var.expr.ts + ^^^^^^^^^ + source.ts meta.var.expr.ts support.type.builtin.ts +>foo(inlineSourceMap) + ^^^ + source.ts meta.function-call.ts entity.name.function.ts + ^ + source.ts meta.brace.round.ts + ^^^^^^^^^^^^^^^ + source.ts variable.other.readwrite.ts + ^ + source.ts meta.brace.round.ts +> + ^ + source.ts +>const e = (f as + ^^^^^ + source.ts meta.var.expr.ts storage.type.ts + ^ + source.ts meta.var.expr.ts + ^ + source.ts meta.var.expr.ts meta.var-single-variable.expr.ts meta.definition.variable.ts variable.other.constant.ts + ^ + source.ts meta.var.expr.ts meta.var-single-variable.expr.ts + ^ + source.ts meta.var.expr.ts keyword.operator.assignment.ts + ^ + source.ts meta.var.expr.ts + ^ + source.ts meta.var.expr.ts meta.brace.round.ts + ^ + source.ts meta.var.expr.ts variable.other.readwrite.ts + ^ + source.ts meta.var.expr.ts + ^^ + source.ts meta.var.expr.ts keyword.control.as.ts +> G); + ^^ + source.ts meta.var.expr.ts + ^ + source.ts meta.var.expr.ts entity.name.type.ts + ^ + source.ts meta.var.expr.ts meta.brace.round.ts + ^ + source.ts punctuation.terminator.statement.ts +> + ^ + source.ts \ No newline at end of file diff --git a/tests/cases/Issue1079.ts b/tests/cases/Issue1079.ts new file mode 100644 index 00000000..423bf80d --- /dev/null +++ b/tests/cases/Issue1079.ts @@ -0,0 +1,12 @@ +const a = b as + A | undefined + +const a = b satisfies + A | undefined + +const inlineSourceMap = convertSourceMap.fromSource(content)?.toObject() as + SourceMap | undefined +foo(inlineSourceMap) + +const e = (f as + G);