diff --git a/Sources/EvolutionMetadataExtraction/Extractors/EvolutionMetadataExtractor.swift b/Sources/EvolutionMetadataExtraction/Extractors/EvolutionMetadataExtractor.swift index 55dc87f..836d13a 100644 --- a/Sources/EvolutionMetadataExtraction/Extractors/EvolutionMetadataExtractor.swift +++ b/Sources/EvolutionMetadataExtraction/Extractors/EvolutionMetadataExtractor.swift @@ -169,6 +169,7 @@ struct ProposalSpec: Sendable { let project: Project let url: URL let sha: String + let number: Int let sortIndex: Int var id: String { "\(project.proposalPrefix)-\(url.lastPathComponent.prefix(4))" } var filename: String { url.lastPathComponent } @@ -177,6 +178,7 @@ struct ProposalSpec: Sendable { self.project = project self.url = url self.sha = sha + self.number = Int(url.lastPathComponent.prefix(4)) ?? -1 self.sortIndex = sortIndex } } diff --git a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/DiscussionExtractor.swift b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/DiscussionExtractor.swift index e168288..9805e21 100644 --- a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/DiscussionExtractor.swift +++ b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/DiscussionExtractor.swift @@ -11,12 +11,15 @@ import EvolutionMetadataModel struct DiscussionExtractor: MarkupWalker, ValueExtractor { + private var source: HeaderFieldSource + init(source: HeaderFieldSource) { self.source = source } + private var warnings: [Proposal.Issue] = [] private var errors: [Proposal.Issue] = [] private var discussions: [Proposal.Discussion] = [] - mutating func extractValue(from source: HeaderFieldSource) -> ExtractionResult<[Proposal.Discussion]> { + mutating func extractValue() -> ExtractionResult<[Proposal.Discussion]> { // VALIDATION ENHANCEMENT: Normalize naming to 'Review' in the source proposals. if let (_, headerField) = source["Review", "Reviews", "Decision Notes", "Decision notes"] { diff --git a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/HeaderFieldExtractor.swift b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/HeaderFieldExtractor.swift index c01f25b..4122472 100644 --- a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/HeaderFieldExtractor.swift +++ b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/HeaderFieldExtractor.swift @@ -12,13 +12,16 @@ import EvolutionMetadataModel // VALIDATION ENHANCEMENT: Add test that checks against valid header field labels struct HeaderFieldExtractor: MarkupWalker, ValueExtractor { - private var headerItemsByLabel: [String: ListItem] = [:] + private var source: DocumentSource + init(source: DocumentSource) { self.source = source } private var warnings: [Proposal.Issue] = [] private var errors: [Proposal.Issue] = [] - - mutating func extractValue(from src: DocumentSource) -> ExtractionResult<[String: ListItem]> { - guard let headerFields = src.document.child(through: [(1, UnorderedList.self)]) as? UnorderedList else { + + private var headerItemsByLabel: [String: ListItem] = [:] + + mutating func extractValue() -> ExtractionResult<[String: ListItem]> { + guard let headerFields = source.document.child(through: [(1, UnorderedList.self)]) as? UnorderedList else { return ExtractionResult(value: nil, warnings: warnings, errors: errors) } visit(headerFields) diff --git a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/ImplementationExtractor.swift b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/ImplementationExtractor.swift index a0e79da..b88c7fb 100644 --- a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/ImplementationExtractor.swift +++ b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/ImplementationExtractor.swift @@ -12,6 +12,9 @@ import EvolutionMetadataModel struct ImplementationExtractor: MarkupWalker, ValueExtractor { + private var source: HeaderFieldSource + init(source: HeaderFieldSource) { self.source = source } + private var warnings: [Proposal.Issue] = [] private var errors: [Proposal.Issue] = [] @@ -20,7 +23,7 @@ struct ImplementationExtractor: MarkupWalker, ValueExtractor { } private var _implementaton: [Proposal.Implementation] = [] - mutating func extractValue(from source: HeaderFieldSource) -> ExtractionResult<[Proposal.Implementation]> { + mutating func extractValue() -> ExtractionResult<[Proposal.Implementation]> { if let (_ , headerField) = source["Implementation", "Implementations"] { visit(headerField) } diff --git a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/PersonExtractor.swift b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/PersonExtractor.swift index 92f9c46..b0a50e7 100644 --- a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/PersonExtractor.swift +++ b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/PersonExtractor.swift @@ -10,16 +10,20 @@ import Markdown import EvolutionMetadataModel struct AuthorExtractor: ValueExtractor { - func extractValue(from src: HeaderFieldSource) -> ExtractionResult<[Proposal.Person]> { - var personExtractor = PersonExtractor(role: .author) - return personExtractor.personArray(from: src) + private let source: HeaderFieldSource + init(source: HeaderFieldSource) { self.source = source } + func extractValue() -> ExtractionResult<[Proposal.Person]> { + var personExtractor = PersonExtractor(source: source, role: .author) + return personExtractor.personArray() } } struct ReviewManagerExtractor: ValueExtractor { - func extractValue(from src: HeaderFieldSource) -> ExtractionResult<[Proposal.Person]> { - var personExtractor = PersonExtractor(role: .reviewManager) - return personExtractor.personArray(from: src) + private let source: HeaderFieldSource + init(source: HeaderFieldSource) { self.source = source } + func extractValue() -> ExtractionResult<[Proposal.Person]> { + var personExtractor = PersonExtractor(source: source, role: .reviewManager) + return personExtractor.personArray() } } @@ -28,19 +32,20 @@ struct PersonExtractor: MarkupWalker { case author case reviewManager } - + + private var source: HeaderFieldSource private var role: Role + init(source: HeaderFieldSource, role: Role) { + self.source = source + self.role = role + } private var warnings: [Proposal.Issue] = [] private var errors: [Proposal.Issue] = [] private var personList: [Proposal.Person] = [] - - init(role: Role) { - self.role = role - } - - mutating func personArray(from source: HeaderFieldSource) -> ExtractionResult<[Proposal.Person]> { + + mutating func personArray() -> ExtractionResult<[Proposal.Person]> { let headerLabels = switch role { case .author: ["Author", "Authors"] // VALIDATION ENHANCEMENT: Normalize capitalization to 'Review Manager' diff --git a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/PreviousProposalExtractor.swift b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/PreviousProposalExtractor.swift index b48eeed..b438276 100644 --- a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/PreviousProposalExtractor.swift +++ b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/PreviousProposalExtractor.swift @@ -10,16 +10,19 @@ import Markdown import EvolutionMetadataModel struct PreviousProposalExtractor: MarkupWalker, ValueExtractor { - + + private var source: HeaderFieldSource + init(source: HeaderFieldSource) { self.source = source } + private var warnings: [Proposal.Issue] = [] private var errors: [Proposal.Issue] = [] - + private var previousProposalIDs: [String]? { _previousProposalIDs.isEmpty ? nil : _previousProposalIDs } private var _previousProposalIDs: [String] = [] - - mutating func extractValue(from source: HeaderFieldSource) -> ExtractionResult<[String]> { + + mutating func extractValue() -> ExtractionResult<[String]> { if let prevPropField = source["Previous Proposal", "Previous Proposals"] { visit(prevPropField.value) diff --git a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/ProposalLinkExtractor.swift b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/ProposalLinkExtractor.swift index ab10c25..046cf47 100644 --- a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/ProposalLinkExtractor.swift +++ b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/ProposalLinkExtractor.swift @@ -11,11 +11,15 @@ import EvolutionMetadataModel struct ProposalLinkExtractor: MarkupWalker, ValueExtractor { - private var proposalLink: LinkInfo? = nil + private var source: HeaderFieldSource + init(source: HeaderFieldSource) { self.source = source } + private var warnings: [Proposal.Issue] = [] private var errors: [Proposal.Issue] = [] - - mutating func extractValue(from source: HeaderFieldSource) -> ExtractionResult { + + private var proposalLink: LinkInfo? = nil + + mutating func extractValue() -> ExtractionResult { if let headerField = source["Proposal"] { visit(headerField) } else { @@ -35,7 +39,7 @@ struct ProposalLinkExtractor: MarkupWalker, ValueExtractor { errors.append(.reservedProposalID) } - if !proposalLink.text.contains(source.proposalSpec.project.proposalRegex) { + if !proposalLink.text.contains(source.project.proposalRegex) { self.proposalLink?.destination = "" // Do not include an incorrect destination errors.append(.proposalIDWrongDigitCount) } @@ -54,10 +58,6 @@ struct ProposalLinkExtractor: MarkupWalker, ValueExtractor { } mutating func visitLink(_ link: Link) -> () { - guard let value = LinkInfo(link: link) else { - errors.append(.missingProposalIDLink) - return - } - proposalLink = value + proposalLink = LinkInfo(link: link) } } diff --git a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/StatusExtractor.swift b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/StatusExtractor.swift index b818c97..072e190 100644 --- a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/StatusExtractor.swift +++ b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/StatusExtractor.swift @@ -11,19 +11,23 @@ import Markdown import EvolutionMetadataModel struct StatusExtractor: MarkupWalker, ValueExtractor { - + + private var source: HeaderFieldSource + private var extractionDate: Date = Date() + init(source: (source: HeaderFieldSource, extractionDate: Date)) { + self.source = source.source + self.extractionDate = source.extractionDate + } + private var warnings: [Proposal.Issue] = [] private var errors: [Proposal.Issue] = [] - private var extractionDate: Date = Date() var status: Proposal.Status? = nil - mutating func extractValue(from sourceValues: (source: HeaderFieldSource, extractionDate: Date)) -> ExtractionResult { - - extractionDate = sourceValues.extractionDate + mutating func extractValue() -> ExtractionResult { // If 'Status' field not found, report - if let headerField = sourceValues.source["Status"] { + if let headerField = source["Status"] { visit(headerField) } @@ -34,7 +38,6 @@ struct StatusExtractor: MarkupWalker, ValueExtractor { return ExtractionResult(value: status, warnings: warnings, errors: errors) } - mutating func visitStrong(_ strong: Strong) -> () { guard let statusElement = strong.child(at: 0) as? Text else { // VALIDATION ENHANCEMENT: Add warning or error malformed / misformatted status @@ -71,7 +74,7 @@ struct StatusExtractor: MarkupWalker, ValueExtractor { status = .statusExtractionFailed } } - + static func versionForString(_ fullVersionString: String) -> String { guard !fullVersionString.isEmpty else { return "none" // If empty string, return 'none' as a sentinel value @@ -117,13 +120,13 @@ struct StatusExtractor: MarkupWalker, ValueExtractor { // For a date range, only 1 or 2 month values are valid guard monthMatches.count != 0 && monthMatches.count < 3 else { // VALIDATION ENHANCEMENT -// print("ERROR: '\(string)': Unexpected month count of \(monthMatches.count)") + // print("ERROR: '\(string)': Unexpected month count of \(monthMatches.count)") return nil } let startMonth = String(monthMatches[0].0) let endMonth = monthMatches.count == 2 ? String(monthMatches[1].0) : startMonth -// print(startMonth, endMonth) + // print(startMonth, endMonth) // Only interested in numbers of one or two digits let dayMatches = string.matches(of: integerMatcher).filter { $0.count <= 2 } diff --git a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/SummaryExtractor.swift b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/SummaryExtractor.swift index 67aaa4c..8e38004 100644 --- a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/SummaryExtractor.swift +++ b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/SummaryExtractor.swift @@ -10,15 +10,18 @@ import Markdown import EvolutionMetadataModel struct SummaryExtractor: ValueExtractor { - + + private var source: DocumentSource + init(source: DocumentSource) { self.source = source } + private var warnings: [Proposal.Issue] = [] private var errors: [Proposal.Issue] = [] - - func extractValue(from src: DocumentSource) -> ExtractionResult { - + + func extractValue() -> ExtractionResult { + var summary = "" var foundIntroduction = false - for child in src.document.children { + for child in source.document.children { // VALIDATION ENHANCEMENT: Potential for stricter validation of section heading and contents // https://github.com/swiftlang/swift-evolution-metadata-extractor/issues/77 diff --git a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/TitleExtractor.swift b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/TitleExtractor.swift index 2800f24..9d6d751 100644 --- a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/TitleExtractor.swift +++ b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/TitleExtractor.swift @@ -10,15 +10,18 @@ import Markdown import EvolutionMetadataModel struct TitleExtractor: ValueExtractor { - + + private var source: DocumentSource + init(source: DocumentSource) { self.source = source } + private var warnings: [Proposal.Issue] = [] private var errors: [Proposal.Issue] = [] - - mutating func extractValue(from src: DocumentSource) -> ExtractionResult { + + mutating func extractValue() -> ExtractionResult { var title: String? - if let titleElement = src.document.child(at: 0) as? Heading { + if let titleElement = source.document.child(at: 0) as? Heading { // VALIDATION ENHANCEMENT: Add warning or error that the title is badly formatted in the markdown // if titleElement.level != 1 { diff --git a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/TrackingBugExtractor.swift b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/TrackingBugExtractor.swift index 958a3c8..ccb91e2 100644 --- a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/TrackingBugExtractor.swift +++ b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/TrackingBugExtractor.swift @@ -11,6 +11,9 @@ import EvolutionMetadataModel struct TrackingBugExtractor: MarkupWalker, ValueExtractor { + private var source: HeaderFieldSource + init(source: HeaderFieldSource) { self.source = source } + private var warnings: [Proposal.Issue] = [] private var errors: [Proposal.Issue] = [] @@ -18,8 +21,8 @@ struct TrackingBugExtractor: MarkupWalker, ValueExtractor { _trackingBugs.isEmpty ? nil : _trackingBugs } private var _trackingBugs: [Proposal.TrackingBug] = [] - - mutating func extractValue(from source: HeaderFieldSource) -> ExtractionResult<[Proposal.TrackingBug]> { + + mutating func extractValue() -> ExtractionResult<[Proposal.TrackingBug]> { if let (_, headerField) = source["Bug", "Bugs"] { visit(headerField) } diff --git a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/UpcomingFeatureFlagExtractor.swift b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/UpcomingFeatureFlagExtractor.swift index 1035e0d..077918e 100644 --- a/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/UpcomingFeatureFlagExtractor.swift +++ b/Sources/EvolutionMetadataExtraction/Extractors/FieldExtractors/UpcomingFeatureFlagExtractor.swift @@ -10,12 +10,15 @@ import Markdown import EvolutionMetadataModel struct UpcomingFeatureFlagExtractor: MarkupWalker, ValueExtractor { - + + private var source: HeaderFieldSource + init(source: HeaderFieldSource) { self.source = source } + private var warnings: [Proposal.Issue] = [] private var errors: [Proposal.Issue] = [] - + private var flag: String? - + private var uff: Proposal.UpcomingFeatureFlag? { if let flag { return Proposal.UpcomingFeatureFlag(flag: flag) @@ -24,7 +27,7 @@ struct UpcomingFeatureFlagExtractor: MarkupWalker, ValueExtractor { } } - mutating func extractValue(from source: HeaderFieldSource) -> ExtractionResult { + mutating func extractValue() -> ExtractionResult { if let uffField = source["Upcoming Feature Flag"] { visit(uffField) diff --git a/Sources/EvolutionMetadataExtraction/Extractors/ProposalMetadataExtractor.swift b/Sources/EvolutionMetadataExtraction/Extractors/ProposalMetadataExtractor.swift index 976d740..6c3c3db 100644 --- a/Sources/EvolutionMetadataExtraction/Extractors/ProposalMetadataExtractor.swift +++ b/Sources/EvolutionMetadataExtraction/Extractors/ProposalMetadataExtractor.swift @@ -10,19 +10,6 @@ import Foundation import Markdown import EvolutionMetadataModel -struct DocumentSource { - let proposalSpec: ProposalSpec - let document: Document -} - -struct HeaderFieldSource { - let proposalSpec: ProposalSpec - let headerFieldsByLabel: [String : ListItem] - subscript(key: String) -> ListItem? { headerFieldsByLabel[key] } - subscript(key: [String]) -> (key: String, value: ListItem)? { headerFieldsByLabel[key] } - subscript(key: String...) -> (key: String, value: ListItem)? { headerFieldsByLabel[key] } -} - struct ProposalMetadataExtractor { /// Extracts the metadata from a Swift Evolution proposal @@ -40,8 +27,8 @@ struct ProposalMetadataExtractor { var errors: [Proposal.Issue] = [] func extractValue(from source: T.Source, with extractorType: T.Type) -> T.ResultValue? { - var extractor = extractorType.init() - let result = extractor.extractValue(from: source) + var extractor = extractorType.init(source: source) + let result = extractor.extractValue() warnings.append(contentsOf: result.warnings) errors.append(contentsOf: result.errors) return result.value @@ -123,8 +110,8 @@ struct ProposalMetadataExtractor { protocol ValueExtractor { associatedtype Source associatedtype ResultValue - mutating func extractValue(from source: Source) -> ExtractionResult - init() + init(source: Source) + mutating func extractValue() -> ExtractionResult } /* Returns an optional value and parsing or validation warnings and errors. @@ -189,5 +176,38 @@ struct LinkInfo { } } +// Protocol and structs to encapsulate source information for extractors + +protocol IssueSource { + var project: Project { get } + var proposalNumber: Int { get } +} + +struct DocumentSource: IssueSource { + private let proposalSpec: ProposalSpec + let document: Document + var project: Project { proposalSpec.project } + var proposalNumber: Int { proposalSpec.number } + init(proposalSpec: ProposalSpec, document: Document) { + self.proposalSpec = proposalSpec + self.document = document + } +} + +struct HeaderFieldSource: IssueSource { + let proposalSpec: ProposalSpec + let headerFieldsByLabel: [String : ListItem] + var project: Project { proposalSpec.project } + var proposalNumber: Int { proposalSpec.number } + subscript(key: String) -> ListItem? { headerFieldsByLabel[key] } + subscript(key: [String]) -> (key: String, value: ListItem)? { headerFieldsByLabel[key] } + subscript(key: String...) -> (key: String, value: ListItem)? { headerFieldsByLabel[key] } + init(proposalSpec: ProposalSpec, headerFieldsByLabel: [String : ListItem]) { + self.proposalSpec = proposalSpec + self.headerFieldsByLabel = headerFieldsByLabel + } +} + +