package printer import ( "fmt" "slices" "strconv" "strings" "unicode/utf8" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/core" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/stringutil" "efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath" ) type getLiteralTextFlags int const ( getLiteralTextFlagsNone getLiteralTextFlags = 0 getLiteralTextFlagsNeverAsciiEscape getLiteralTextFlags = 1 << 0 getLiteralTextFlagsJsxAttributeEscape getLiteralTextFlags = 1 << 1 getLiteralTextFlagsTerminateUnterminatedLiterals getLiteralTextFlags = 1 << 2 getLiteralTextFlagsAllowNumericSeparator getLiteralTextFlags = 1 << 3 ) type QuoteChar rune const ( QuoteCharSingleQuote QuoteChar = '\'' QuoteCharDoubleQuote QuoteChar = '"' QuoteCharBacktick QuoteChar = '`' ) var jsxEscapedCharsMap = map[rune]string{ '"': """, '\'': "'", } var escapedCharsMap = map[rune]string{ '\t': `\t`, '\v': `\v`, '\f': `\f`, '\b': `\b`, '\r': `\r`, '\n': `\n`, '\\': `\\`, '"': `\"`, '\'': `\'`, '`': "\\`", '$': `\$`, // when quoteChar == '`' '\u2028': `\u2028`, // lineSeparator '\u2029': `\u2029`, // paragraphSeparator '\u0085': `\u0085`, // nextLine } func encodeJsxCharacterEntity(b *strings.Builder, charCode rune) { hexCharCode := strings.ToUpper(strconv.FormatUint(uint64(charCode), 16)) b.WriteString("&#x") b.WriteString(hexCharCode) b.WriteByte(';') } func encodeUtf16EscapeSequence(b *strings.Builder, charCode rune) { hexCharCode := strings.ToUpper(strconv.FormatUint(uint64(charCode), 16)) b.WriteString(`\u`) for i := len(hexCharCode); i < 4; i++ { b.WriteByte('0') } b.WriteString(hexCharCode) } // Based heavily on the abstract 'Quote'/'QuoteJSONString' operation from ECMA-262 (24.3.2.2), // but augmented for a few select characters (e.g. lineSeparator, paragraphSeparator, nextLine) // Note that this doesn't actually wrap the input in double quotes. func escapeStringWorker(s string, quoteChar QuoteChar, flags getLiteralTextFlags, b *strings.Builder) { pos := 0 i := 0 for i < len(s) { ch, size := utf8.DecodeRuneInString(s[i:]) escape := false // This consists of the first 19 unprintable ASCII characters, canonical escapes, lineSeparator, // paragraphSeparator, and nextLine. The latter three are just desirable to suppress new lines in // the language service. These characters should be escaped when printing, and if any characters are added, // `escapedCharsMap` and/or `jsxEscapedCharsMap` must be updated. Note that this *does not* include the 'delete' // character. There is no reason for this other than that JSON.stringify does not handle it either. switch ch { case '\\': if flags&getLiteralTextFlagsJsxAttributeEscape == 0 { escape = true } case '$': if quoteChar == QuoteCharBacktick && i+1 < len(s) && s[i+1] == '{' { escape = true } case rune(quoteChar), '\u2028', '\u2029', '\u0085', '\r': escape = true case '\n': if quoteChar != QuoteCharBacktick { // Template strings preserve simple LF newlines, still encode CRLF (or CR). escape = true } default: if ch <= '\u001f' || flags&getLiteralTextFlagsNeverAsciiEscape == 0 && ch > '\u007f' { escape = true } } if escape { if pos < i { // Write string up to this point b.WriteString(s[pos:i]) } switch { case flags&getLiteralTextFlagsJsxAttributeEscape != 0: if ch == 0 { b.WriteString("�") } else if match, ok := jsxEscapedCharsMap[ch]; ok { b.WriteString(match) } else { encodeJsxCharacterEntity(b, ch) } default: if ch == '\r' && quoteChar == QuoteCharBacktick && i+1 < len(s) && s[i+1] == '\n' { // Template strings preserve simple LF newlines, but still must escape CRLF. Left alone, the // above cases for `\r` and `\n` would inadvertently escape CRLF as two independent characters. size++ b.WriteString(`\r\n`) } else if ch > 0xffff { // encode as surrogate pair ch -= 0x10000 encodeUtf16EscapeSequence(b, (ch&0b11111111110000000000>>10)+0xD800) encodeUtf16EscapeSequence(b, (ch&0b00000000001111111111)+0xDC00) } else if ch == 0 { if i+1 < len(s) && stringutil.IsDigit(rune(s[i+1])) { // If the null character is followed by digits, print as a hex escape to prevent the result from // parsing as an octal (which is forbidden in strict mode) b.WriteString(`\x00`) } else { // Otherwise, keep printing a literal \0 for the null character b.WriteString(`\0`) } } else { if match, ok := escapedCharsMap[ch]; ok { b.WriteString(match) } else { encodeUtf16EscapeSequence(b, ch) } } } pos = i + size } i += size } if pos < i { b.WriteString(s[pos:]) } } func EscapeString(s string, quoteChar QuoteChar) string { var b strings.Builder b.Grow(len(s) + 2) escapeStringWorker(s, quoteChar, getLiteralTextFlagsNeverAsciiEscape, &b) return b.String() } func escapeNonAsciiString(s string, quoteChar QuoteChar) string { var b strings.Builder b.Grow(len(s) + 2) escapeStringWorker(s, quoteChar, getLiteralTextFlagsNone, &b) return b.String() } func escapeJsxAttributeString(s string, quoteChar QuoteChar) string { var b strings.Builder b.Grow(len(s) + 2) escapeStringWorker(s, quoteChar, getLiteralTextFlagsJsxAttributeEscape|getLiteralTextFlagsNeverAsciiEscape, &b) return b.String() } func canUseOriginalText(node *ast.LiteralLikeNode, flags getLiteralTextFlags) bool { // A synthetic node has no original text, nor does a node without a parent as we would be unable to find the // containing SourceFile. We also cannot use the original text if the literal was unterminated and the caller has // requested proper termination of unterminated literals if ast.NodeIsSynthesized(node) || node.Parent == nil || flags&getLiteralTextFlagsTerminateUnterminatedLiterals != 0 && ast.IsUnterminatedLiteral(node) { return false } if node.Kind == ast.KindNumericLiteral { tokenFlags := node.AsNumericLiteral().TokenFlags // For a numeric literal, we cannot use the original text if the original text was an invalid literal if tokenFlags&ast.TokenFlagsIsInvalid != 0 { return false } // We also cannot use the original text if the literal contains numeric separators, but numeric separators // are not permitted if tokenFlags&ast.TokenFlagsContainsSeparator != 0 { return flags&getLiteralTextFlagsAllowNumericSeparator != 0 } } // Finally, we do not use the original text of a BigInt literal // TODO(rbuckton): The reason as to why we do not use the original text for bigints is not mentioned in the // original compiler source. It could be that this is no longer necessary, in which case bigint literals should // use the same code path as numeric literals, above return node.Kind != ast.KindBigIntLiteral } func getLiteralText(node *ast.LiteralLikeNode, sourceFile *ast.SourceFile, flags getLiteralTextFlags) string { // If we don't need to downlevel and we can reach the original source text using // the node's parent reference, then simply get the text as it was originally written. if sourceFile != nil && canUseOriginalText(node, flags) { return scanner.GetSourceTextOfNodeFromSourceFile(sourceFile, node, false /*includeTrivia*/) } // If we can't reach the original source text, use the canonical form if it's a number, // or a (possibly escaped) quoted form of the original text if it's string-like. switch node.Kind { case ast.KindStringLiteral: var b strings.Builder var quoteChar QuoteChar if node.AsStringLiteral().TokenFlags&ast.TokenFlagsSingleQuote != 0 { quoteChar = QuoteCharSingleQuote } else { quoteChar = QuoteCharDoubleQuote } text := node.Text() // Write leading quote character b.Grow(len(text) + 2) b.WriteRune(rune(quoteChar)) // Write text escapeStringWorker(text, quoteChar, flags, &b) // Write trailing quote character b.WriteRune(rune(quoteChar)) return b.String() case ast.KindNoSubstitutionTemplateLiteral, ast.KindTemplateHead, ast.KindTemplateMiddle, ast.KindTemplateTail: // If a NoSubstitutionTemplateLiteral appears to have a substitution in it, the original text // had to include a backslash: `not \${a} substitution`. var b strings.Builder text := node.TemplateLiteralLikeData().Text rawText := node.TemplateLiteralLikeData().RawText raw := len(rawText) > 0 || len(text) == 0 var textLen int if raw { textLen = len(rawText) } else { textLen = len(text) } // Write leading quote character switch node.Kind { case ast.KindNoSubstitutionTemplateLiteral: b.Grow(2 + textLen) b.WriteRune('`') case ast.KindTemplateHead: b.Grow(3 + textLen) b.WriteRune('`') case ast.KindTemplateMiddle: b.Grow(3 + textLen) b.WriteRune('}') case ast.KindTemplateTail: b.Grow(2 + textLen) b.WriteRune('}') } // Write text switch { case len(rawText) > 0 || len(text) == 0: // If rawText is set, it is expected to be valid. b.WriteString(rawText) default: escapeStringWorker(text, QuoteCharBacktick, flags, &b) } // Write trailing quote character switch node.Kind { case ast.KindNoSubstitutionTemplateLiteral: b.WriteRune('`') case ast.KindTemplateHead: b.WriteString("${") case ast.KindTemplateMiddle: b.WriteString("${") case ast.KindTemplateTail: b.WriteRune('`') } return b.String() case ast.KindNumericLiteral, ast.KindBigIntLiteral: return node.Text() case ast.KindRegularExpressionLiteral: if flags&getLiteralTextFlagsTerminateUnterminatedLiterals != 0 && ast.IsUnterminatedLiteral(node) { var b strings.Builder text := node.Text() if len(text) > 0 && text[len(text)-1] == '\\' { b.Grow(2 + len(text)) b.WriteString(text) b.WriteString(" /") } else { b.Grow(1 + len(text)) b.WriteString(text) b.WriteString("/") } return b.String() } return node.Text() default: panic("Unsupported LiteralLikeNode") } } func isNotPrologueDirective(node *ast.Node) bool { return !ast.IsPrologueDirective(node) } func rangeIsOnSingleLine(r core.TextRange, sourceFile *ast.SourceFile) bool { return rangeStartIsOnSameLineAsRangeEnd(r, r, sourceFile) } func rangeStartPositionsAreOnSameLine(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile) bool { return positionsAreOnSameLine( getStartPositionOfRange(range1, sourceFile, false /*includeComments*/), getStartPositionOfRange(range2, sourceFile, false /*includeComments*/), sourceFile, ) } func rangeEndPositionsAreOnSameLine(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile) bool { return positionsAreOnSameLine(range1.End(), range2.End(), sourceFile) } func rangeStartIsOnSameLineAsRangeEnd(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile) bool { return positionsAreOnSameLine(getStartPositionOfRange(range1, sourceFile, false /*includeComments*/), range2.End(), sourceFile) } func rangeEndIsOnSameLineAsRangeStart(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile) bool { return positionsAreOnSameLine(range1.End(), getStartPositionOfRange(range2, sourceFile, false /*includeComments*/), sourceFile) } func getStartPositionOfRange(r core.TextRange, sourceFile *ast.SourceFile, includeComments bool) int { if ast.PositionIsSynthesized(r.Pos()) { return -1 } return scanner.SkipTriviaEx(sourceFile.Text(), r.Pos(), &scanner.SkipTriviaOptions{StopAtComments: includeComments}) } func positionsAreOnSameLine(pos1 int, pos2 int, sourceFile *ast.SourceFile) bool { return GetLinesBetweenPositions(sourceFile, pos1, pos2) == 0 } func GetLinesBetweenPositions(sourceFile *ast.SourceFile, pos1 int, pos2 int) int { if pos1 == pos2 { return 0 } lineStarts := scanner.GetECMALineStarts(sourceFile) lower := core.IfElse(pos1 < pos2, pos1, pos2) isNegative := lower == pos2 upper := core.IfElse(isNegative, pos1, pos2) lowerLine := scanner.ComputeLineOfPosition(lineStarts, lower) upperLine := lowerLine + scanner.ComputeLineOfPosition(lineStarts[lowerLine:], upper) if isNegative { return lowerLine - upperLine } else { return upperLine - lowerLine } } func getLinesBetweenRangeEndAndRangeStart(range1 core.TextRange, range2 core.TextRange, sourceFile *ast.SourceFile, includeSecondRangeComments bool) int { range2Start := getStartPositionOfRange(range2, sourceFile, includeSecondRangeComments) return GetLinesBetweenPositions(sourceFile, range1.End(), range2Start) } func getLinesBetweenPositionAndPrecedingNonWhitespaceCharacter(pos int, stopPos int, sourceFile *ast.SourceFile, includeComments bool) int { startPos := scanner.SkipTriviaEx(sourceFile.Text(), pos, &scanner.SkipTriviaOptions{StopAtComments: includeComments}) prevPos := getPreviousNonWhitespacePosition(startPos, stopPos, sourceFile) return GetLinesBetweenPositions(sourceFile, core.IfElse(prevPos >= 0, prevPos, stopPos), startPos) } func getLinesBetweenPositionAndNextNonWhitespaceCharacter(pos int, stopPos int, sourceFile *ast.SourceFile, includeComments bool) int { nextPos := scanner.SkipTriviaEx(sourceFile.Text(), pos, &scanner.SkipTriviaOptions{StopAtComments: includeComments}) return GetLinesBetweenPositions(sourceFile, pos, core.IfElse(stopPos < nextPos, stopPos, nextPos)) } func getPreviousNonWhitespacePosition(pos int, stopPos int, sourceFile *ast.SourceFile) int { for ; pos >= stopPos; pos-- { if !stringutil.IsWhiteSpaceLike(rune(sourceFile.Text()[pos])) { return pos } } return -1 } func getCommentRange(node *ast.Node) core.TextRange { // TODO(rbuckton) return node.Loc } func siblingNodePositionsAreComparable(previousNode *ast.Node, nextNode *ast.Node) bool { if nextNode.Pos() < previousNode.End() { return false } // TODO(rbuckton) // previousNode = getOriginalNode(previousNode); // nextNode = getOriginalNode(nextNode); parent := previousNode.Parent if parent == nil || parent != nextNode.Parent { return false } parentNodeArray := getContainingNodeArray(previousNode) if parentNodeArray != nil { prevNodeIndex := slices.Index(parentNodeArray.Nodes, previousNode) return prevNodeIndex >= 0 && slices.Index(parentNodeArray.Nodes, nextNode) == prevNodeIndex+1 } return false } func getContainingNodeArray(node *ast.Node) *ast.NodeList { parent := node.Parent if parent == nil { return nil } switch node.Kind { case ast.KindTypeParameter: switch { case ast.IsFunctionLike(parent): return parent.FunctionLikeData().TypeParameters case ast.IsClassLike(parent): return parent.ClassLikeData().TypeParameters case ast.IsInterfaceDeclaration(parent): return parent.AsInterfaceDeclaration().TypeParameters case ast.IsTypeOrJSTypeAliasDeclaration(parent): return parent.AsTypeAliasDeclaration().TypeParameters case ast.IsInferTypeNode(parent): break default: panic(fmt.Sprintf("Unexpected TypeParameter parent: %#v", parent.Kind)) } case ast.KindParameter: return node.Parent.FunctionLikeData().Parameters case ast.KindTemplateLiteralTypeSpan: return node.Parent.AsTemplateLiteralTypeNode().TemplateSpans case ast.KindTemplateSpan: return node.Parent.AsTemplateExpression().TemplateSpans case ast.KindDecorator: if canHaveDecorators(node.Parent) { if modifiers := node.Parent.Modifiers(); modifiers != nil { return &modifiers.NodeList } } return nil case ast.KindHeritageClause: if ast.IsClassLike(node.Parent) { return node.Parent.ClassLikeData().HeritageClauses } else { return node.Parent.AsInterfaceDeclaration().HeritageClauses } } // TODO(rbuckton) // if ast.IsJSDocTag(node) { // if ast.IsJSDocTypeLiteral(node.parent) { // return nil // } // return node.parent.tags // } switch parent.Kind { case ast.KindTypeLiteral: if ast.IsTypeElement(node) { return parent.AsTypeLiteralNode().Members } case ast.KindInterfaceDeclaration: if ast.IsTypeElement(node) { return parent.AsInterfaceDeclaration().Members } case ast.KindUnionType: return parent.AsUnionTypeNode().Types case ast.KindIntersectionType: return parent.AsIntersectionTypeNode().Types case ast.KindTupleType: return parent.AsTupleTypeNode().Elements case ast.KindArrayLiteralExpression: return parent.AsArrayLiteralExpression().Elements case ast.KindCommaListExpression: panic("not implemented") case ast.KindNamedImports: return parent.AsNamedImports().Elements case ast.KindNamedExports: return parent.AsNamedExports().Elements case ast.KindObjectLiteralExpression: return parent.AsObjectLiteralExpression().Properties case ast.KindJsxAttributes: return parent.AsJsxAttributes().Properties case ast.KindCallExpression: p := parent.AsCallExpression() switch { case ast.IsTypeNode(node): return p.TypeArguments case node != p.Expression: return p.Arguments } case ast.KindNewExpression: p := parent.AsNewExpression() switch { case ast.IsTypeNode(node): return p.TypeArguments case node != p.Expression: return p.Arguments } case ast.KindJsxElement: if ast.IsJsxChild(node) { return parent.AsJsxElement().Children } case ast.KindJsxFragment: if ast.IsJsxChild(node) { return parent.AsJsxFragment().Children } case ast.KindJsxOpeningElement: if ast.IsTypeNode(node) { return parent.AsJsxOpeningElement().TypeArguments } case ast.KindJsxSelfClosingElement: if ast.IsTypeNode(node) { return parent.AsJsxSelfClosingElement().TypeArguments } case ast.KindBlock: return parent.AsBlock().Statements case ast.KindCaseClause, ast.KindDefaultClause: return parent.AsCaseOrDefaultClause().Statements case ast.KindModuleBlock: return parent.AsModuleBlock().Statements case ast.KindCaseBlock: return parent.AsCaseBlock().Clauses case ast.KindClassDeclaration, ast.KindClassExpression: if ast.IsClassElement(node) { return parent.ClassLikeData().Members } case ast.KindEnumDeclaration: if ast.IsEnumMember(node) { return parent.AsEnumDeclaration().Members } case ast.KindSourceFile: if ast.IsStatement(node) { return parent.AsSourceFile().Statements } } if ast.IsModifier(node) { if modifiers := parent.Modifiers(); modifiers != nil { return &modifiers.NodeList } } return nil } func canHaveDecorators(node *ast.Node) bool { switch node.Kind { case ast.KindParameter, ast.KindPropertyDeclaration, ast.KindMethodDeclaration, ast.KindGetAccessor, ast.KindSetAccessor, ast.KindClassExpression, ast.KindClassDeclaration: return true } return false } func originalNodesHaveSameParent(nodeA *ast.Node, nodeB *ast.Node) bool { // TODO(rbuckton): nodeA = getOriginalNode(nodeA) if nodeA.Parent != nil { // For performance, do not call `getOriginalNode` for `nodeB` if `nodeA` doesn't even // have a parent node. // TODO(rbuckton): nodeB = getOriginalNode(nodeB) return nodeA.Parent == nodeB.Parent } return false } func tryGetEnd(node interface{ End() int }) (int, bool) { // avoid using reflect (via core.IsNil) for common cases switch v := node.(type) { case (*ast.Node): if v != nil { return v.End(), true } case (*ast.NodeList): if v != nil { return v.End(), true } case (*ast.ModifierList): if v != nil { return v.End(), true } case (*core.TextRange): if v != nil { return v.End(), true } case (core.TextRange): return v.End(), true default: panic(fmt.Sprintf("unhandled type: %T", node)) } return 0, false } func greatestEnd(end int, nodes ...interface{ End() int }) int { for i := len(nodes) - 1; i >= 0; i-- { node := nodes[i] if nodeEnd, ok := tryGetEnd(node); ok && end < nodeEnd { end = nodeEnd } } return end } func skipSynthesizedParentheses(node *ast.Node) *ast.Node { for node.Kind == ast.KindParenthesizedExpression && ast.NodeIsSynthesized(node) { node = node.AsParenthesizedExpression().Expression } return node } func isNewExpressionWithoutArguments(node *ast.Node) bool { return node.Kind == ast.KindNewExpression && node.AsNewExpression().Arguments == nil } func isBinaryOperation(node *ast.Node, token ast.Kind) bool { node = ast.SkipPartiallyEmittedExpressions(node) return node.Kind == ast.KindBinaryExpression && node.AsBinaryExpression().OperatorToken.Kind == token } func isImmediatelyInvokedFunctionExpressionOrArrowFunction(node *ast.Expression) bool { node = ast.SkipPartiallyEmittedExpressions(node) if !ast.IsCallExpression(node) { return false } node = ast.SkipPartiallyEmittedExpressions(node.AsCallExpression().Expression) return ast.IsFunctionExpression(node) || ast.IsArrowFunction(node) } func IsFileLevelUniqueName(sourceFile *ast.SourceFile, name string, hasGlobalName func(string) bool) bool { if hasGlobalName != nil && hasGlobalName(name) { return false } _, ok := sourceFile.Identifiers[name] return !ok } func hasLeadingHash(text string) bool { return len(text) > 0 && text[0] == '#' } func removeLeadingHash(text string) string { if hasLeadingHash(text) { return text[1:] } else { return text } } func ensureLeadingHash(text string) string { if hasLeadingHash(text) { return text } else { return "#" + text } } func FormatGeneratedName(privateName bool, prefix string, base string, suffix string) string { name := removeLeadingHash(prefix) + removeLeadingHash(base) + removeLeadingHash(suffix) if privateName { return ensureLeadingHash(name) } return name } func isASCIIWordCharacter(ch rune) bool { return stringutil.IsASCIILetter(ch) || stringutil.IsDigit(ch) || ch == '_' } func makeIdentifierFromModuleName(moduleName string) string { moduleName = tspath.GetBaseFileName(moduleName) var builder strings.Builder start := 0 pos := 0 for pos < len(moduleName) { ch := rune(moduleName[pos]) if pos == 0 && stringutil.IsDigit(ch) { builder.WriteByte('_') } else if !isASCIIWordCharacter(ch) { if start < pos { builder.WriteString(moduleName[start:pos]) } builder.WriteByte('_') start = pos + 1 } pos++ } if start < pos { builder.WriteString(moduleName[start:pos]) } return builder.String() } func findSpanEndWithEmitContext[T any](c *EmitContext, array []T, test func(c *EmitContext, value T) bool, start int) int { i := start for i < len(array) && test(c, array[i]) { i++ } return i } func findSpanEnd[T any](array []T, test func(value T) bool, start int) int { i := start for i < len(array) && test(array[i]) { i++ } return i } func skipWhiteSpaceSingleLine(text string, pos *int) { for *pos < len(text) { ch, size := utf8.DecodeRuneInString(text[*pos:]) if !stringutil.IsWhiteSpaceSingleLine(ch) { break } *pos += size } } func matchWhiteSpaceSingleLine(text string, pos *int) bool { startPos := *pos skipWhiteSpaceSingleLine(text, pos) return *pos != startPos } func matchRune(text string, pos *int, expected rune) bool { ch, size := utf8.DecodeRuneInString(text[*pos:]) if ch == expected { *pos += size return true } return false } func matchString(text string, pos *int, expected string) bool { textPos := *pos expectedPos := 0 for expectedPos < len(expected) { if textPos >= len(text) { return false } expectedRune, expectedSize := utf8.DecodeRuneInString(expected[expectedPos:]) if !matchRune(text, &textPos, expectedRune) { return false } expectedPos += expectedSize } *pos = textPos return true } func matchQuotedString(text string, pos *int) bool { textPos := *pos var quoteChar rune switch { case matchRune(text, &textPos, '\''): quoteChar = '\'' case matchRune(text, &textPos, '"'): quoteChar = '"' default: return false } for textPos < len(text) { ch, size := utf8.DecodeRuneInString(text[textPos:]) textPos += size if ch == quoteChar { *pos = textPos return true } } return false } // /// // /// // /// // /// // /// // /// func IsRecognizedTripleSlashComment(text string, commentRange ast.CommentRange) bool { if commentRange.Kind == ast.KindSingleLineCommentTrivia && commentRange.Len() > 2 && text[commentRange.Pos()+1] == '/' && text[commentRange.Pos()+2] == '/' { text = text[commentRange.Pos()+3 : commentRange.End()] pos := 0 skipWhiteSpaceSingleLine(text, &pos) if !matchRune(text, &pos, '<') { return false } switch { case matchString(text, &pos, "reference"): if !matchWhiteSpaceSingleLine(text, &pos) { return false } if !matchString(text, &pos, "path") && !matchString(text, &pos, "types") && !matchString(text, &pos, "lib") && !matchString(text, &pos, "no-default-lib") { return false } skipWhiteSpaceSingleLine(text, &pos) if !matchRune(text, &pos, '=') { return false } skipWhiteSpaceSingleLine(text, &pos) if !matchQuotedString(text, &pos) { return false } case matchString(text, &pos, "amd-dependency"): if !matchWhiteSpaceSingleLine(text, &pos) { return false } if !matchString(text, &pos, "path") { return false } skipWhiteSpaceSingleLine(text, &pos) if !matchRune(text, &pos, '=') { return false } skipWhiteSpaceSingleLine(text, &pos) if !matchQuotedString(text, &pos) { return false } case matchString(text, &pos, "amd-module"): skipWhiteSpaceSingleLine(text, &pos) default: return false } index := strings.Index(text[pos:], "/>") return index != -1 } return false } func isJSDocLikeText(text string, comment ast.CommentRange) bool { return comment.Kind == ast.KindMultiLineCommentTrivia && comment.Len() > 5 && text[comment.Pos()+2] == '*' && text[comment.Pos()+3] != '/' } func IsPinnedComment(text string, comment ast.CommentRange) bool { return comment.Kind == ast.KindMultiLineCommentTrivia && comment.Len() > 5 && text[comment.Pos()+2] == '!' } func calculateIndent(text string, pos int, end int) int { currentLineIndent := 0 indentSize := len(getIndentString(1)) for pos < end { ch, size := utf8.DecodeRuneInString(text[pos:]) if !stringutil.IsWhiteSpaceSingleLine(ch) { break } if ch == '\t' { // Tabs = TabSize = indent size and go to next tabStop currentLineIndent += indentSize - (currentLineIndent % indentSize) } else { // Single space currentLineIndent++ } pos += size } return currentLineIndent }