File: CommentParsing.swift

package info (click to toggle)
swiftlang 6.0.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 2,519,992 kB
  • sloc: cpp: 9,107,863; ansic: 2,040,022; asm: 1,135,751; python: 296,500; objc: 82,456; f90: 60,502; lisp: 34,951; pascal: 19,946; sh: 18,133; perl: 7,482; ml: 4,937; javascript: 4,117; makefile: 3,840; awk: 3,535; xml: 914; fortran: 619; cs: 573; ruby: 573
file content (141 lines) | stat: -rw-r--r-- 4,877 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2023 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
//

import SwiftSyntax

/// Find a common whitespace prefix among all lines in a string and trim it.
///
/// - Parameters:
///   - string: The string to trim.
///
/// - Returns: A copy of `string` with leading whitespace trimmed, or `string`
///   verbatim if all lines do not share a common whitespace prefix.
private func _trimCommonLeadingWhitespaceFromLines(in string: String) -> String {
  var lines = string.split(whereSeparator: \.isNewline)

  var firstLine = lines.first
  while let firstCharacter = firstLine?.first, firstCharacter.isWhitespace {
    defer {
      firstLine?.removeFirst()
    }
    if lines.lazy.map(\.first).allSatisfy({ $0 == firstCharacter }) {
      lines = lines.map { $0.dropFirst() }
    }
  }

  return lines.joined(separator: "\n")
}

/// Trim an instance of `Trivia` and return its pieces after the last
/// double-newline sequence (if one is present.)
///
/// - Parameters:
///   - trivia: The trivia to inspect.
///
/// - Returns: A subset of the pieces in `trivia` following the last
///   double-newline sequence (ignoring other whitespace.) All whitespace trivia
///   pieces are removed from the result.
///
/// This function allows ``createCommentTraitExprs(for:)`` to ignore unrelated
/// comments that are included in the leading trivia of some syntax node. For
/// example:
///
/// ```swift
/// /* Not relevant */
///
/// /* Relevant */
/// #expect(...)
/// ```
///
/// `/* Not relevant */` is not included in the result, while `/* Relevant */`
/// is included.
private func _trimTriviaToLastDoubleNewline(_ trivia: Trivia) -> some Sequence<TriviaPiece> {
  var result = [TriviaPiece]()

  // Walk the trivia pieces backwards until we hit a double-newline. We'll take
  // this loop as an opportunity to do some additional cleanup as well.
  for triviaPiece in trivia.pieces.reversed() {
    // Check if we've hit a double-newline.
    if let newlineCount = triviaPiece.newlineCount {
      if let lastTriviaPiece = result.last, lastTriviaPiece.isNewline {
        break
      } else if newlineCount > 1 {
        break
      }
    }

    if triviaPiece.isWhitespace && !triviaPiece.isNewline {
      // Tack whitespace onto the preceding trivia piece (which is next in
      // source because we're iterating backwards) if it's a comment. That way,
      // indentation is consistent among all lines in the comment. After this
      // loop concludes, we'll strip off that whitespace.
      //
      // For example:
      // ___/* x
      // ___   y */
      let newLastPiece: TriviaPiece?
      switch result.last {
      case let .some(.docBlockComment(comment)):
        newLastPiece = .docBlockComment("\(triviaPiece)\(comment)")
      case let .some(.blockComment(comment)):
        newLastPiece = .blockComment("\(triviaPiece)\(comment)")
      default:
        newLastPiece = nil
      }
      if let newLastPiece {
        result[result.count - 1] = newLastPiece
      }
    } else {
      // Preserve newlines and non-whitespace trivia pieces.
      result.append(triviaPiece)
    }
  }

  // Trim common leading whitespace from block comments. Remember that we added
  // leading whitespace above, and that whitespace is what we should be trimming
  // in this loop.
  result = result.map { triviaPiece in
    switch triviaPiece {
    case let .docBlockComment(comment):
      return .docBlockComment(_trimCommonLeadingWhitespaceFromLines(in: comment))
    case let .blockComment(comment):
      return .blockComment(_trimCommonLeadingWhitespaceFromLines(in: comment))
    default:
      return triviaPiece
    }
  }

  return result.reversed().lazy.filter { !$0.isWhitespace }
}

/// Create an expression that contains an array of ``Comment`` instances based
/// on the code comments present on the specified node.
///
/// - Parameters:
///   - node: The node which may contain code comments.
///
/// - Returns: An array of expressions producing ``Comment`` instances. If
///   `node` has no code comments, an empty array is returned.
func createCommentTraitExprs(for node: some SyntaxProtocol) -> [ExprSyntax] {
  _trimTriviaToLastDoubleNewline(node.leadingTrivia).compactMap { triviaPiece in
    switch triviaPiece {
    case .lineComment(let comment):
      ".__line(\(literal: comment))"
    case .blockComment(let comment):
      ".__block(\(literal: comment))"
    case .docLineComment(let comment):
      ".__documentationLine(\(literal: comment))"
    case .docBlockComment(let comment):
      ".__documentationBlock(\(literal: comment))"
    default:
      nil
    }
  }
}