@@ -5,50 +5,26 @@ import Foundation
55let usage = """
66./ \( CommandLine . arguments [ 0 ] ) <swift-source-directory> [output-directory]
77
8- Generates index files for diagnostics notes, groups, and upcoming features.
8+ Generates index files for diagnostics groups and upcoming features.
99"""
1010
1111let docsDir = " userdocs/diagnostics "
1212let topLevelFileName = " diagnostics.md "
1313
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-
3814let groupsDocFileName = " diagnostic-groups.md "
3915let groupsHeader = """
4016# Diagnostic groups
4117
4218<!-- This file is auto-generated via `swift swift/utils/generate-doc-index.swift` -->
4319
44- Diagnostic groups allow controlling the behavior of warnings in a more precise manner .
20+ Detailed explanations for various compiler diagnostics .
4521
4622
4723## Overview
4824
4925Diagnostic groups collect some number of diagnostics together under a common group name. This allows
5026for 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:
27+ control the behavior of warnings in a more precise manner (when that group contains warnings) :
5228- `-Werror <group>` - upgrades warnings in the specified group to errors
5329- `-Wwarning <group>` - indicates that warnings in the specified group should remain warnings, even
5430 if they were previously upgraded to errors
@@ -62,11 +38,6 @@ Or upgrade all warnings except deprecated declaration to errors:
6238```sh
6339-warnings-as-errors -Wwarning DeprecatedDeclaration
6440```
65-
66-
67- ## Topics
68-
69-
7041"""
7142
7243let featuresDocFileName = " upcoming-language-features.md "
@@ -88,18 +59,17 @@ enabled on the command line with `-enable-upcoming-feature <feature>`.
8859Some upcoming features have an additional " migration " mode, where the compiler will emit warnings
8960with fix-its to help migrate to that mode. This can be enabled with `-enable-upcoming-feature
9061<feature>:migrate`.
62+ """
9163
64+ let topicsHeader = " \n \n ## Topics \n "
9265
93- ## Topics
94-
66+ let swiftIncludeDir = " include/swift "
9567
96- """
68+ let groupsFileName = " \( swiftIncludeDir) /AST/DiagnosticGroups.def "
69+ let groupRegex = /GROUP\((?<name>[a-zA-Z]+), "(?<file>.+)"\)/
9770
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]+), .+\)/
71+ let featuresFileName = " \( swiftIncludeDir) /Basic/Features.def "
72+ let featuresRegex = /UPCOMING_FEATURE\((?<name>[a-zA-Z]+), .+\)/
10373
10474let nameRegex = /# .+ \((?<name>[a-zA-Z]+)\)/
10575
@@ -117,126 +87,206 @@ if !args.isEmpty {
11787 outputDir = " \( swiftSourceDir) / \( docsDir) "
11888}
11989
120- let generator = GenerateUserDocs ( swiftSourceDir: swiftSourceDir, outputDir: outputDir)
12190do {
122- try generator . generateIndex ( )
91+ try generateIndex ( )
12392} catch {
12493 print ( " error: \( error) " )
12594 exit ( 1 )
12695}
12796
128- struct GenerateUserDocs {
129- let swiftSourceDir : String
130- let outputDir : String
97+ func generateIndex( ) throws {
98+ let groupsHandle = try createIndex ( name: groupsDocFileName, header: groupsHeader)
99+ defer { try ? groupsHandle. close ( ) }
100+
101+ let featuresHandle = try createIndex ( name: featuresDocFileName, header: featuresHeader)
102+ defer { try ? featuresHandle. close ( ) }
131103
132- func generateIndex( ) throws {
133- let notesHandle = try createIndex ( name: notesDocFileName, header: notesHeader)
134- defer { try ? notesHandle. close ( ) }
104+ let groupsWithWarnings = try groupNamesWithWarnings ( )
105+ let docs = try retrieveDocs ( groupsWithWarnings) . sorted { a, b in
106+ return a. title < b. title
107+ }
135108
136- let groupsHandle = try createIndex ( name: groupsDocFileName, header: groupsHeader)
137- defer { try ? groupsHandle. close ( ) }
109+ try groupsHandle. write ( contentsOf: " \n \n ## Groups with warnings \n " . data ( using: . utf8) !)
110+ for doc in docs where doc. kind == . groupWithWarnings {
111+ let ref = " - <doc: \( doc. name. dropLast ( 3 ) ) > \n "
112+ try groupsHandle. write ( contentsOf: ref. data ( using: . utf8) !)
113+ }
138114
139- let featuresHandle = try createIndex ( name : featuresDocFileName , header : featuresHeader )
140- defer { try ? featuresHandle. close ( ) }
115+ try groupsHandle . write ( contentsOf : topicsHeader . data ( using : . utf8 ) ! )
116+ try featuresHandle. write ( contentsOf : topicsHeader . data ( using : . utf8 ) ! )
141117
142- let docs = try retrieveDocs ( ) . sorted { a, b in
143- return a. title < b. title
118+ for doc in docs {
119+ let handle : FileHandle
120+ switch doc. kind {
121+ case . group, . groupWithWarnings:
122+ handle = groupsHandle
123+ case . feature:
124+ handle = featuresHandle
144125 }
145126
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- }
127+ let ref = " - <doc: \( doc. name. dropLast ( 3 ) ) > \n "
128+ try handle. write ( contentsOf: ref. data ( using: . utf8) !)
129+ }
130+ }
156131
157- let ref = " - <doc: \( doc. name. dropLast ( 3 ) ) > \n "
158- try handle. write ( contentsOf: ref. data ( using: . utf8) !)
159- }
132+ func createIndex( name: String , header: String ) throws -> FileHandle {
133+ let path = " \( outputDir) / \( name) "
134+
135+ if FileManager . default. fileExists ( atPath: path) {
136+ try FileManager . default. removeItem ( atPath: path)
160137 }
138+ FileManager . default. createFile ( atPath: path, contents: nil )
139+
140+ let handle = try FileHandle ( forWritingTo: URL ( filePath: path) )
141+ try handle. write ( contentsOf: header. data ( using: . utf8) !)
142+ return handle
143+ }
161144
162- func createIndex( name: String , header: String ) throws -> FileHandle {
163- let path = " \( outputDir) / \( name) "
145+ func retrieveDocs( _ groupsWithWarnings: Set < String > ) throws -> [ UserDoc ] {
146+ let groups = Dictionary ( try matches ( in: " \( swiftSourceDir) / \( groupsFileName) " , with: groupRegex) {
147+ ( file: String ( $0. file) , name: String ( $0. name) )
148+ } , uniquingKeysWith: { a, b in a } )
149+ let features = Set ( try matches ( in: " \( swiftSourceDir) / \( featuresFileName) " , with: featuresRegex) {
150+ String ( $0. 1 )
151+ } )
152+
153+ var docs : [ UserDoc ] = [ ]
154+
155+ let files = try FileManager . default. contentsOfDirectory ( atPath: " \( swiftSourceDir) / \( docsDir) " )
156+ for name in files {
157+ if !name. hasSuffix ( " .md " )
158+ || name. hasSuffix ( topLevelFileName)
159+ || name. hasSuffix ( groupsDocFileName)
160+ || name. hasSuffix ( featuresDocFileName) {
161+ continue
162+ }
163+
164+ guard let groupName = groups [ String ( name. dropLast ( 3 ) ) ] else {
165+ throw GenerationError . unknownGroup ( file: name)
166+ }
164167
165- if FileManager . default. fileExists ( atPath: path) {
166- try FileManager . default. removeItem ( atPath: path)
168+ let path = try String ( contentsOfFile: " \( swiftSourceDir) / \( docsDir) / \( name) " , encoding: . utf8)
169+ guard let match = try ? nameRegex. prefixMatch ( in: path) else {
170+ throw GenerationError . missingGroup ( name: groupName, file: name)
167171 }
168- FileManager . default. createFile ( atPath: path, contents: nil )
169172
170- let handle = try FileHandle ( forWritingTo: URL ( filePath: path) )
171- try handle. write ( contentsOf: header. data ( using: . utf8) !)
172- return handle
173+ let titleGroupName = String ( match. name)
174+ if groupName != titleGroupName {
175+ throw GenerationError . incorrectGroup ( defsName: groupName, titleName: titleGroupName, file: name)
176+ }
177+
178+ let kind : UserDoc . Kind
179+ if features. contains ( groupName) {
180+ kind = . feature
181+ } else if groupsWithWarnings. contains ( groupName) {
182+ kind = . groupWithWarnings
183+ } else {
184+ kind = . group
185+ }
186+
187+ docs. append ( UserDoc ( name: name, title: String ( match. 0 ) , kind: kind) )
173188 }
174189
175- func matches ( in fileName : String , with regex : Regex < ( Substring , Substring ) > ) throws -> Set < String > {
176- let file = try String ( contentsOfFile : " \( swiftSourceDir ) / \( fileName ) " , encoding : . utf8 )
190+ return docs
191+ }
177192
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 ) )
193+ func groupNamesWithWarnings( ) throws -> Set < String > {
194+ let includePath = " \( swiftSourceDir) / \( swiftIncludeDir) "
195+ let defPaths = try FileManager . default. subpathsOfDirectory ( atPath: includePath)
196+ . compactMap { subpath in
197+ if subpath. hasSuffix ( " .def " ) {
198+ return " \( includePath) / \( subpath) "
182199 }
200+ return nil
183201 }
184202
185- return matches
203+ enum WarningGroupState {
204+ case outside, inside, name
186205 }
187206
188- func retrieveDocs ( ) throws -> [ UserDoc ] {
189- let groups = try matches ( in : groupsFileName , with : groupRegex )
190- let features = try matches ( in : featuresFileName , with : featuresRegex )
207+ var groups : Set < String > = [ ]
208+ for path in defPaths {
209+ let file = try String ( contentsOfFile : path , encoding : . utf8 )
191210
192- var docs : [ UserDoc ] = [ ]
211+ var state = WarningGroupState . outside
212+ for line in file. components ( separatedBy: . newlines) {
213+ var line = Substring ( line)
193214
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- }
215+ switch state {
216+ case . outside:
217+ if !line. hasPrefix ( " GROUPED_WARNING " ) {
218+ continue
219+ }
220+
221+ state = . inside
222+ fallthrough
203223
204- let file = try String ( contentsOfFile: " \( swiftSourceDir) / \( docsDir) / \( name) " , encoding: . utf8)
224+ case . inside:
225+ guard let index = line. firstIndex ( of: " , " ) else {
226+ continue
227+ }
228+ line = line [ index... ] . dropFirst ( )
205229
206- if let match = try ? nameRegex . prefixMatch ( in : file ) {
207- let groupName = String ( match . name )
230+ state = . name
231+ fallthrough
208232
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
233+ case . name:
234+ if let index = line. firstIndex ( of: " , " ) {
235+ line = line [ ..< index]
216236 }
237+ line = line. trimmingPrefix { $0. isWhitespace }
217238
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) )
239+ if line. isEmpty {
240+ continue
224241 }
242+
243+ groups. insert ( String ( line) )
244+ state = . outside
225245 }
226246 }
247+ }
248+
249+ return groups
250+ }
227251
228- return docs
252+ func matches< R, E> ( in path: String , with regex: Regex < R > , _ transform: ( Regex < R > . Match ) -> E ) throws -> [ E ] {
253+ let file = try String ( contentsOfFile: path, encoding: . utf8)
254+
255+ var matches = [ E] ( )
256+ for line in file. components ( separatedBy: . newlines) {
257+ if let match = try ? regex. firstMatch ( in: line) {
258+ matches. append ( transform ( match) )
259+ }
229260 }
261+
262+ return matches
230263}
231264
232265struct UserDoc {
233266 enum Kind {
234- case note
235267 case group
268+ case groupWithWarnings
236269 case feature
237270 }
238271
239272 let name : String
240273 let title : String
241274 let kind : Kind
242275}
276+
277+ enum GenerationError : CustomStringConvertible , Error {
278+ case incorrectGroup( defsName: String , titleName: String , file: String )
279+ case missingGroup( name: String , file: String )
280+ case unknownGroup( file: String )
281+
282+ var description : String {
283+ switch self {
284+ case . incorrectGroup( let defsName, let titleName, let file) :
285+ return " The title in ' \( file) ' contains the name ' \( titleName) ', but it should be ' \( defsName) ' as per 'DiagnosticGroups.def' "
286+ case . missingGroup( let name, let file) :
287+ return " The title in ' \( file) ' does not end with a group name, add ' ( \( name) )' to the end of the title "
288+ case . unknownGroup( let file) :
289+ return " ' \( file) ' has no corresponding listing in 'DiagnosticGroups.def' "
290+ }
291+ }
292+ }
0 commit comments