Skip to content

Commit a45a334

Browse files
committed
use SwiftSyntax to parse decl signatures ourselves instead of relying on SymbolGraphGen's (awful) output
1 parent 4b87b8d commit a45a334

21 files changed

+651
-287
lines changed

Assets/css/Main.css

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Assets/css/Main.css.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Package.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,9 @@ let package:Package = .init(
236236
.target(name: "MarkdownPluginSwift", dependencies:
237237
[
238238
.target(name: "MarkdownABI"),
239+
.target(name: "Signatures"),
240+
.target(name: "Symbols"),
241+
239242
.product(name: "IDEUtils", package: "swift-syntax"),
240243
.product(name: "SwiftParser", package: "swift-syntax"),
241244
]),
@@ -330,6 +333,7 @@ let package:Package = .init(
330333
[
331334
.target(name: "JSON"),
332335
.target(name: "LexicalPaths"),
336+
.target(name: "MarkdownPluginSwift"),
333337
.target(name: "ModuleGraphs"),
334338
.target(name: "Signatures"),
335339
.target(name: "Symbols"),
@@ -481,6 +485,13 @@ let package:Package = .init(
481485
.product(name: "Testing", package: "swift-grammar"),
482486
]),
483487

488+
.executableTarget(name: "MarkdownPluginSwiftTests", dependencies:
489+
[
490+
.target(name: "MarkdownPluginSwift"),
491+
.target(name: "MarkdownRendering"),
492+
.product(name: "Testing", package: "swift-grammar"),
493+
]),
494+
484495
.executableTarget(name: "MarkdownRenderingTests", dependencies:
485496
[
486497
.target(name: "MarkdownRendering"),

Sources/MarkdownABI/Bytecode/MarkdownBytecode.Context.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ extension MarkdownBytecode
5555
case `class`
5656
case type
5757
case `typealias`
58-
/// Argument label, used to line-break long function signatures.
5958
/// New in 8.0.
6059
case label
6160

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import IDEUtils
2+
import MarkdownABI
3+
4+
extension MarkdownBytecode.Context
5+
{
6+
init?(classification:SyntaxClassification)
7+
{
8+
switch classification
9+
{
10+
case .none,
11+
.editorPlaceholder: return nil
12+
13+
case .attribute: self = .attribute
14+
15+
case .buildConfigId: self = .directive
16+
case .poundDirectiveKeyword: self = .magic
17+
18+
case .lineComment,
19+
.blockComment: self = .comment
20+
case .docLineComment,
21+
.docBlockComment: self = .doccomment
22+
23+
case .dollarIdentifier: self = .pseudo
24+
case .identifier: self = .identifier
25+
case .operatorIdentifier: self = .operator
26+
27+
case .integerLiteral,
28+
.floatingLiteral: self = .literalNumber
29+
case .stringLiteral,
30+
.objectLiteral: self = .literalString
31+
32+
case .keyword: self = .keyword
33+
case .stringInterpolationAnchor: self = .interpolation
34+
case .typeIdentifier: self = .type
35+
}
36+
}
37+
}

Sources/MarkdownPluginSwift/MarkdownCodeLanguage.Swift.Highlighter.swift

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -36,37 +36,14 @@ extension MarkdownCodeLanguage.Swift.Highlighter:MarkdownCodeHighlighter
3636
start: base + span.offset,
3737
count: span.length)
3838

39-
let context:MarkdownBytecode.Context
40-
switch span.kind
39+
if let context:MarkdownBytecode.Context = .init(classification: span.kind)
4140
{
42-
case .none,
43-
.editorPlaceholder: binary += text ; continue
44-
45-
case .attribute: context = .attribute
46-
47-
case .buildConfigId: context = .directive
48-
case .poundDirectiveKeyword: context = .magic
49-
50-
case .lineComment,
51-
.blockComment: context = .comment
52-
case .docLineComment,
53-
.docBlockComment: context = .doccomment
54-
55-
case .dollarIdentifier: context = .pseudo
56-
case .identifier: context = .identifier
57-
case .operatorIdentifier: context = .operator
58-
59-
case .integerLiteral,
60-
.floatingLiteral: context = .literalNumber
61-
case .stringLiteral,
62-
.objectLiteral: context = .literalString
63-
64-
case .keyword: context = .keyword
65-
case .stringInterpolationAnchor: context = .interpolation
66-
case .typeIdentifier: context = .type
41+
binary[context] { $0 += text }
42+
}
43+
else
44+
{
45+
binary += text
6746
}
68-
69-
binary[context] { $0 += text }
7047
}
7148
}
7249
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import MarkdownABI
2+
import Signatures
3+
4+
extension Signature.Abridged
5+
{
6+
@inlinable public
7+
init(_ fragments:__shared some Sequence<Signature<Scalar>.Fragment>, actor:Bool = false)
8+
{
9+
var utf8:[UInt8] = []
10+
for fragment:Signature.Fragment in fragments
11+
{
12+
utf8 += fragment.spelling.utf8
13+
}
14+
self.init(utf8: utf8, actor: actor)
15+
}
16+
17+
@_spi(testable) public
18+
init(_ string:String)
19+
{
20+
self.init(utf8: [UInt8].init(string.utf8))
21+
}
22+
23+
@usableFromInline internal
24+
init(utf8:[UInt8], actor:Bool = false)
25+
{
26+
let regions:SignatureSyntax = utf8.withUnsafeBufferPointer(SignatureSyntax.init)
27+
let bytecode:MarkdownBytecode = .init
28+
{
29+
for token:SignatureSyntax.Token? in regions.tokens
30+
{
31+
guard let token:SignatureSyntax.Token
32+
else
33+
{
34+
$0[.wbr]
35+
continue
36+
}
37+
38+
let text:String = .init(decoding: utf8[token.range], as: Unicode.UTF8.self)
39+
40+
if actor,
41+
case (.keyword, "class") = (token.color, text)
42+
{
43+
$0 += "actor"
44+
continue
45+
}
46+
47+
switch (token.color, text)
48+
{
49+
case (.identifier, _),
50+
(.keyword, "init"),
51+
(.keyword, "deinit"),
52+
(.keyword, "subscript"):
53+
$0[.identifier] = text
54+
55+
case (.label, _):
56+
$0[.label] = text
57+
58+
case _:
59+
$0 += text
60+
}
61+
}
62+
}
63+
self.init(bytecode: bytecode)
64+
}
65+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
2+
3+
import MarkdownABI
4+
import Signatures
5+
6+
extension Signature.Expanded
7+
{
8+
@inlinable public
9+
init(_ fragments:__shared some Collection<Signature<Scalar>.Fragment>,
10+
keywords:inout InterestingKeywords)
11+
{
12+
var utf8:[UInt8] = []
13+
utf8.reserveCapacity(fragments.reduce(0) { $0 + $1.spelling.utf8.count })
14+
15+
var symbols:[Int: Scalar] = [:]
16+
for fragment:Signature.Fragment in fragments
17+
{
18+
let i:Int = utf8.endIndex
19+
utf8 += fragment.spelling.utf8
20+
21+
if let referent:Scalar = fragment.referent
22+
{
23+
symbols[i] = referent
24+
}
25+
}
26+
27+
self.init(utf8: utf8, keywords: &keywords, symbols: &symbols)
28+
29+
if !symbols.isEmpty
30+
{
31+
fatalError("syntax didn’t round-trip, failed to match symbols: \(symbols)")
32+
}
33+
}
34+
35+
@inlinable @_spi(testable) public
36+
init(_ string:String)
37+
{
38+
var keywords:InterestingKeywords = .init()
39+
var empty:[Int: Scalar] = [:]
40+
self.init(utf8: [UInt8].init(string.utf8), keywords: &keywords, symbols: &empty)
41+
}
42+
43+
@inlinable internal
44+
init(utf8:[UInt8], keywords:inout InterestingKeywords, symbols:inout [Int: Scalar])
45+
{
46+
let regions:SignatureSyntax = utf8.withUnsafeBufferPointer(SignatureSyntax.init)
47+
var references:[Scalar: Int] = [:]
48+
var referents:[Scalar] = []
49+
50+
let bytecode:MarkdownBytecode = .init
51+
{
52+
for token:SignatureSyntax.Token? in regions.tokens
53+
{
54+
guard let token:SignatureSyntax.Token
55+
else
56+
{
57+
$0[.wbr]
58+
continue
59+
}
60+
61+
let text:String = .init(decoding: utf8[token.range], as: Unicode.UTF8.self)
62+
63+
guard let color:MarkdownBytecode.Context = token.color
64+
else
65+
{
66+
$0 += text
67+
continue
68+
}
69+
70+
if case .keyword = color
71+
{
72+
switch text
73+
{
74+
case "actor": keywords.actor = true
75+
case "class": keywords.class = true
76+
default: break
77+
}
78+
}
79+
80+
$0[color]
81+
{
82+
if let referent:Scalar = symbols.removeValue(forKey: token.start)
83+
{
84+
$0[.href] =
85+
{
86+
if let reference:Int = $0
87+
{
88+
return reference
89+
}
90+
else
91+
{
92+
let next:Int = referents.endIndex
93+
referents.append(referent)
94+
$0 = next
95+
return next
96+
}
97+
} (&references[referent])
98+
}
99+
} = text
100+
}
101+
}
102+
103+
self.init(bytecode: bytecode, scalars: referents)
104+
}
105+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import Signatures
2+
3+
extension Signature.Expanded
4+
{
5+
@frozen public
6+
struct InterestingKeywords
7+
{
8+
public
9+
var actor:Bool
10+
public
11+
var `class`:Bool
12+
13+
@inlinable public
14+
init(actor:Bool = false, `class`:Bool = false)
15+
{
16+
self.actor = actor
17+
self.class = `class`
18+
}
19+
}
20+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import Signatures
2+
3+
extension Signature
4+
{
5+
@frozen public
6+
struct Fragment:Equatable, Hashable
7+
{
8+
public
9+
let spelling:String
10+
public
11+
let referent:Scalar?
12+
13+
@inlinable public
14+
init(_ spelling:String, referent:Scalar? = nil)
15+
{
16+
self.spelling = spelling
17+
self.referent = referent
18+
}
19+
}
20+
}
21+
extension Signature.Fragment:Sendable where Scalar:Sendable
22+
{
23+
}
24+
extension Signature.Fragment:ExpressibleByStringLiteral
25+
{
26+
@inlinable public
27+
init(stringLiteral spelling:String)
28+
{
29+
self.init(spelling)
30+
}
31+
}

0 commit comments

Comments
 (0)