|
| 1 | +#!/usr/bin/env swift -enable-upcoming-feature BareSlashRegexLiterals |
| 2 | + |
| 3 | +import Foundation |
| 4 | + |
| 5 | +let usage = """ |
| 6 | +./\(CommandLine.arguments[0]) <swift-source-directory> [output-directory] |
| 7 | +
|
| 8 | +Generates index files for diagnostics notes, groups, and upcoming features. |
| 9 | +""" |
| 10 | + |
| 11 | +let docsDir = "userdocs/diagnostics" |
| 12 | +let topLevelFileName = "diagnostics.md" |
| 13 | + |
| 14 | +let notesDocFileName = "diagnostic-descriptions.md" |
| 15 | +let notesHeader = """ |
| 16 | +# Diagnostic descriptions |
| 17 | +
|
| 18 | +<!-- This file is auto-generated via `swift swift/utils/generate-doc-index.swift` --> |
| 19 | +
|
| 20 | +Detailed explanations for various compiler diagnostics. |
| 21 | +
|
| 22 | +
|
| 23 | +## Overview |
| 24 | +
|
| 25 | +Swift diagnostics are classified into errors and warnings. Warnings can only be silenced in an |
| 26 | +intentional manner, e.g., adding `_ =` for an unused function result. |
| 27 | +
|
| 28 | +Some diagnostics have more detailed explanations available. These include a `[#Name]` inline and |
| 29 | +reference to this documentation at the end of the compiler output on the command line, or is |
| 30 | +presented specially within your IDE of choice. See below for the full list of these notes. |
| 31 | +
|
| 32 | +
|
| 33 | +## Topics |
| 34 | +
|
| 35 | +
|
| 36 | +""" |
| 37 | + |
| 38 | +let groupsDocFileName = "diagnostic-groups.md" |
| 39 | +let groupsHeader = """ |
| 40 | +# Diagnostic groups |
| 41 | +
|
| 42 | +<!-- This file is auto-generated via `swift swift/utils/generate-doc-index.swift` --> |
| 43 | +
|
| 44 | +Diagnostic groups allow controlling the behavior of warnings in a more precise manner. |
| 45 | +
|
| 46 | +
|
| 47 | +## Overview |
| 48 | +
|
| 49 | +Diagnostic groups collect some number of diagnostics together under a common group name. This allows |
| 50 | +for extra documentation to help explain relevant language concepts, as well as the ability to |
| 51 | +control the behavior of warnings in a more precise manner: |
| 52 | +- `-Werror <group>` - upgrades warnings in the specified group to errors |
| 53 | +- `-Wwarning <group>` - indicates that warnings in the specified group should remain warnings, even |
| 54 | + if they were previously upgraded to errors |
| 55 | +
|
| 56 | +As a concrete example, to upgrade deprecated declaration warnings to errors: |
| 57 | +```sh |
| 58 | +-Werror DeprecatedDeclaration |
| 59 | +``` |
| 60 | +
|
| 61 | +Or upgrade all warnings except deprecated declaration to errors: |
| 62 | +```sh |
| 63 | +-warnings-as-errors -Wwarning DeprecatedDeclaration |
| 64 | +``` |
| 65 | +
|
| 66 | +
|
| 67 | +## Topics |
| 68 | +
|
| 69 | +
|
| 70 | +""" |
| 71 | + |
| 72 | +let featuresDocFileName = "upcoming-language-features.md" |
| 73 | +let featuresHeader = """ |
| 74 | +# Upcoming language features |
| 75 | +
|
| 76 | +<!-- This file is auto-generated via `swift swift/utils/generate-doc-index.swift` --> |
| 77 | +
|
| 78 | +Upcoming language features enable new (but potentially source breaking) functionality that be |
| 79 | +enabled by default in an upcoming language mode. |
| 80 | +
|
| 81 | +
|
| 82 | +## Overview |
| 83 | +
|
| 84 | +Upcoming language features allow the incremental adoption of language features that would otherwise |
| 85 | +only be available in a new language mode, without having to fully migrate to that mode. They can be |
| 86 | +enabled on the command line with `-enable-upcoming-feature <feature>`. |
| 87 | +
|
| 88 | +Some upcoming features have an additional "migration" mode, where the compiler will emit warnings |
| 89 | +with fix-its to help migrate to that mode. This can be enabled with `-enable-upcoming-feature |
| 90 | +<feature>:migrate`. |
| 91 | +
|
| 92 | +
|
| 93 | +## Topics |
| 94 | +
|
| 95 | +
|
| 96 | +""" |
| 97 | + |
| 98 | +let groupsFileName = "include/swift/AST/DiagnosticGroups.def" |
| 99 | +let groupRegex = /GROUP\(([a-zA-Z]+), ".+"\)/ |
| 100 | + |
| 101 | +let featuresFileName = "include/swift/Basic/Features.def" |
| 102 | +let featuresRegex = /UPCOMING_FEATURE\(([a-zA-Z]+), .+\)/ |
| 103 | + |
| 104 | +let nameRegex = /# .+ \((?<name>[a-zA-Z]+)\)/ |
| 105 | + |
| 106 | +var args = CommandLine.arguments.dropFirst() |
| 107 | +if args.count != 1 && args.count != 2 { |
| 108 | + print(usage) |
| 109 | + exit(2) |
| 110 | +} |
| 111 | + |
| 112 | +let swiftSourceDir = args.removeFirst() |
| 113 | +let outputDir: String |
| 114 | +if !args.isEmpty { |
| 115 | + outputDir = args.removeFirst() |
| 116 | +} else { |
| 117 | + outputDir = "\(swiftSourceDir)/\(docsDir)" |
| 118 | +} |
| 119 | + |
| 120 | +let generator = GenerateUserDocs(swiftSourceDir: swiftSourceDir, outputDir: outputDir) |
| 121 | +do { |
| 122 | + try generator.generateIndex() |
| 123 | +} catch { |
| 124 | + print("error: \(error)") |
| 125 | + exit(1) |
| 126 | +} |
| 127 | + |
| 128 | +struct GenerateUserDocs { |
| 129 | + let swiftSourceDir: String |
| 130 | + let outputDir: String |
| 131 | + |
| 132 | + func generateIndex() throws { |
| 133 | + let notesHandle = try createIndex(name: notesDocFileName, header: notesHeader) |
| 134 | + defer { try? notesHandle.close() } |
| 135 | + |
| 136 | + let groupsHandle = try createIndex(name: groupsDocFileName, header: groupsHeader) |
| 137 | + defer { try? groupsHandle.close() } |
| 138 | + |
| 139 | + let featuresHandle = try createIndex(name: featuresDocFileName, header: featuresHeader) |
| 140 | + defer { try? featuresHandle.close() } |
| 141 | + |
| 142 | + let docs = try retrieveDocs().sorted { a, b in |
| 143 | + return a.title < b.title |
| 144 | + } |
| 145 | + |
| 146 | + for doc in docs { |
| 147 | + let handle: FileHandle |
| 148 | + switch doc.kind { |
| 149 | + case .note: |
| 150 | + handle = notesHandle |
| 151 | + case .group: |
| 152 | + handle = groupsHandle |
| 153 | + case .feature: |
| 154 | + handle = featuresHandle |
| 155 | + } |
| 156 | + |
| 157 | + let ref = "- <doc:\(doc.name.dropLast(3))>\n" |
| 158 | + try handle.write(contentsOf: ref.data(using: .utf8)!) |
| 159 | + } |
| 160 | + } |
| 161 | + |
| 162 | + func createIndex(name: String, header: String) throws -> FileHandle { |
| 163 | + let path = "\(outputDir)/\(name)" |
| 164 | + |
| 165 | + if FileManager.default.fileExists(atPath: path) { |
| 166 | + try FileManager.default.removeItem(atPath: path) |
| 167 | + } |
| 168 | + FileManager.default.createFile(atPath: path, contents: nil) |
| 169 | + |
| 170 | + let handle = try FileHandle(forWritingTo: URL(filePath: path)) |
| 171 | + try handle.write(contentsOf: header.data(using: .utf8)!) |
| 172 | + return handle |
| 173 | + } |
| 174 | + |
| 175 | + func matches(in fileName: String, with regex: Regex<(Substring, Substring)>) throws -> Set<String> { |
| 176 | + let file = try String(contentsOfFile: "\(swiftSourceDir)/\(fileName)", encoding: .utf8) |
| 177 | + |
| 178 | + var matches: Set<String> = [] |
| 179 | + for line in file.components(separatedBy: .newlines) { |
| 180 | + if let match = try? regex.firstMatch(in: line) { |
| 181 | + matches.insert(String(match.1)) |
| 182 | + } |
| 183 | + } |
| 184 | + |
| 185 | + return matches |
| 186 | + } |
| 187 | + |
| 188 | + func retrieveDocs() throws -> [UserDoc] { |
| 189 | + let groups = try matches(in: groupsFileName, with: groupRegex) |
| 190 | + let features = try matches(in: featuresFileName, with: featuresRegex) |
| 191 | + |
| 192 | + var docs: [UserDoc] = [] |
| 193 | + |
| 194 | + let files = try FileManager.default.contentsOfDirectory(atPath: "\(swiftSourceDir)/\(docsDir)") |
| 195 | + for name in files { |
| 196 | + if !name.hasSuffix(".md") |
| 197 | + || name.hasSuffix(topLevelFileName) |
| 198 | + || name.hasSuffix(notesDocFileName) |
| 199 | + || name.hasSuffix(groupsDocFileName) |
| 200 | + || name.hasSuffix(featuresDocFileName) { |
| 201 | + continue |
| 202 | + } |
| 203 | + |
| 204 | + let file = try String(contentsOfFile: "\(swiftSourceDir)/\(docsDir)/\(name)", encoding: .utf8) |
| 205 | + |
| 206 | + if let match = try? nameRegex.prefixMatch(in: file) { |
| 207 | + let groupName = String(match.name) |
| 208 | + |
| 209 | + let kind: UserDoc.Kind |
| 210 | + if features.contains(groupName) { |
| 211 | + kind = .feature |
| 212 | + } else if groups.contains(groupName) { |
| 213 | + kind = .group |
| 214 | + } else { |
| 215 | + kind = .note |
| 216 | + } |
| 217 | + |
| 218 | + docs.append(UserDoc(name: name, title: String(match.0), kind: kind)) |
| 219 | + } else { |
| 220 | + if let newlineIndex = file.firstIndex(of: "\n") { |
| 221 | + docs.append(UserDoc(name: name, title: String(file[..<newlineIndex]), kind: .note)) |
| 222 | + } else { |
| 223 | + docs.append(UserDoc(name: name, title: file, kind: .note)) |
| 224 | + } |
| 225 | + } |
| 226 | + } |
| 227 | + |
| 228 | + return docs |
| 229 | + } |
| 230 | +} |
| 231 | + |
| 232 | +struct UserDoc { |
| 233 | + enum Kind { |
| 234 | + case note |
| 235 | + case group |
| 236 | + case feature |
| 237 | + } |
| 238 | + |
| 239 | + let name: String |
| 240 | + let title: String |
| 241 | + let kind: Kind |
| 242 | +} |
0 commit comments