diff --git a/Sources/WIT/AST.swift b/Sources/WIT/AST.swift index c6f96eba..497e4c49 100644 --- a/Sources/WIT/AST.swift +++ b/Sources/WIT/AST.swift @@ -68,6 +68,7 @@ public struct PackageNameSyntax: Equatable, Hashable, CustomStringConvertible { } public struct TopLevelUseSyntax: Equatable, Hashable, SyntaxNodeProtocol { + var attributes: [AttributeSyntax] var item: UsePathSyntax var asName: Identifier? } @@ -75,6 +76,7 @@ public struct TopLevelUseSyntax: Equatable, Hashable, SyntaxNodeProtocol { public struct WorldSyntax: Equatable, Hashable, SyntaxNodeProtocol { public typealias Parent = SourceFileSyntax public var documents: DocumentsSyntax + public var attributes: [AttributeSyntax] public var name: Identifier public var items: [WorldItemSyntax] } @@ -89,11 +91,13 @@ public enum WorldItemSyntax: Equatable, Hashable { public struct ImportSyntax: Equatable, Hashable { public var documents: DocumentsSyntax + public var attributes: [AttributeSyntax] public var kind: ExternKindSyntax } public struct ExportSyntax: Equatable, Hashable { public var documents: DocumentsSyntax + public var attributes: [AttributeSyntax] public var kind: ExternKindSyntax } @@ -105,6 +109,7 @@ public enum ExternKindSyntax: Equatable, Hashable { public struct InterfaceSyntax: Equatable, Hashable, CustomStringConvertible, SyntaxNodeProtocol { public var documents: DocumentsSyntax + public var attributes: [AttributeSyntax] public var name: Identifier public var items: [InterfaceItemSyntax] @@ -121,6 +126,7 @@ public enum InterfaceItemSyntax: Equatable, Hashable, SyntaxNodeProtocol { public struct TypeDefSyntax: Equatable, Hashable, SyntaxNodeProtocol { public var documents: DocumentsSyntax + public var attributes: [AttributeSyntax] public var name: Identifier public var body: TypeDefBodySyntax } @@ -240,6 +246,7 @@ public struct StreamSyntax: Equatable, Hashable { public struct NamedFunctionSyntax: Equatable, Hashable, SyntaxNodeProtocol { public var documents: DocumentsSyntax + public var attributes: [AttributeSyntax] public var name: Identifier public var function: FunctionSyntax } @@ -281,6 +288,7 @@ public struct FunctionSyntax: Equatable, Hashable { } public struct UseSyntax: Equatable, Hashable, SyntaxNodeProtocol { + public var attributes: [AttributeSyntax] public var from: UsePathSyntax public var names: [UseNameSyntax] } @@ -303,6 +311,7 @@ public struct UseNameSyntax: Equatable, Hashable { } public struct IncludeSyntax: Equatable, Hashable { + var attributes: [AttributeSyntax] var from: UsePathSyntax var names: [IncludeNameSyntax] } @@ -324,3 +333,25 @@ public struct Identifier: Equatable, Hashable, CustomStringConvertible { public struct DocumentsSyntax: Equatable, Hashable { var comments: [String] } + +public enum AttributeSyntax: Equatable, Hashable { + case since(SinceAttributeSyntax) + case unstable(UnstableAttributeSyntax) + case deprecated(DeprecatedAttributeSyntax) +} + +public struct SinceAttributeSyntax: Equatable, Hashable { + let version: Version + let feature: Identifier? + let textRange: TextRange +} + +public struct UnstableAttributeSyntax: Equatable, Hashable { + let textRange: TextRange + let feature: Identifier +} + +public struct DeprecatedAttributeSyntax: Equatable, Hashable { + let textRange: TextRange + let version: Version +} diff --git a/Sources/WIT/Lexer.swift b/Sources/WIT/Lexer.swift index 88d54a39..81c78cf4 100644 --- a/Sources/WIT/Lexer.swift +++ b/Sources/WIT/Lexer.swift @@ -44,6 +44,12 @@ struct Lexer { } var cursor: Cursor + let requireSemicolon: Bool + + init(cursor: Cursor, requireSemicolon: Bool = false) { + self.cursor = cursor + self.requireSemicolon = requireSemicolon + } mutating func advanceToEndOfBlockComment() -> Diagnostic? { var depth = 1 @@ -245,6 +251,24 @@ struct Lexer { return actual } + @discardableResult + mutating func expectIdentifier(_ expected: String) throws -> Lexer.Lexeme { + let lexme = try self.expect(.id) + let actualText = self.parseText(in: lexme.textRange) + guard actualText == expected else { + throw ParseError(description: "\(expected) expected but got \(actualText)") + } + return lexme + } + + mutating func expectSemicolon() throws { + if self.requireSemicolon { + try self.expect(.semicolon) + } else { + self.eat(.semicolon) + } + } + @discardableResult mutating func eat(_ expected: TokenKind) -> Bool { var other = self diff --git a/Sources/WIT/TextParser/ParseFunctionDecl.swift b/Sources/WIT/TextParser/ParseFunctionDecl.swift index dff1ed74..146f8ea5 100644 --- a/Sources/WIT/TextParser/ParseFunctionDecl.swift +++ b/Sources/WIT/TextParser/ParseFunctionDecl.swift @@ -1,5 +1,9 @@ extension ResourceFunctionSyntax { - static func parse(lexer: inout Lexer, documents: DocumentsSyntax) throws -> ResourceFunctionSyntax { + static func parse( + lexer: inout Lexer, + documents: DocumentsSyntax, + attributes: [AttributeSyntax] + ) throws -> ResourceFunctionSyntax { guard let token = lexer.peek() else { throw ParseError(description: "`constructor` or identifier expected but got nothing") } @@ -18,10 +22,12 @@ extension ResourceFunctionSyntax { let type = try TypeReprSyntax.parse(lexer: &lexer) return ParameterSyntax(name: name, type: type, textRange: start.. SyntaxNode { try lexer.expect(.interface) let name = try Identifier.parse(lexer: &lexer) let items = try parseItems(lexer: &lexer) - return .init(syntax: InterfaceSyntax(documents: documents, name: name, items: items)) + return .init( + syntax: InterfaceSyntax( + documents: documents, attributes: attributes, name: name, items: items + )) } static func parseItems(lexer: inout Lexer) throws -> [InterfaceItemSyntax] { @@ -16,29 +19,30 @@ extension InterfaceSyntax { if lexer.eat(.rightBrace) { break } - items.append(try InterfaceItemSyntax.parse(lexer: &lexer, documents: docs)) + let attributes = try AttributeSyntax.parseItems(lexer: &lexer) + items.append(try InterfaceItemSyntax.parse(lexer: &lexer, documents: docs, attributes: attributes)) } return items } } extension InterfaceItemSyntax { - static func parse(lexer: inout Lexer, documents: DocumentsSyntax) throws -> InterfaceItemSyntax { + static func parse(lexer: inout Lexer, documents: DocumentsSyntax, attributes: [AttributeSyntax]) throws -> InterfaceItemSyntax { switch lexer.peek()?.kind { case .type: - return try .typeDef(.init(syntax: .parse(lexer: &lexer, documents: documents))) + return try .typeDef(.init(syntax: .parse(lexer: &lexer, documents: documents, attributes: attributes))) case .flags: - return try .typeDef(.init(syntax: .parseFlags(lexer: &lexer, documents: documents))) + return try .typeDef(.init(syntax: .parseFlags(lexer: &lexer, documents: documents, attributes: attributes))) case .enum: - return try .typeDef(.init(syntax: .parseEnum(lexer: &lexer, documents: documents))) + return try .typeDef(.init(syntax: .parseEnum(lexer: &lexer, documents: documents, attributes: attributes))) case .variant: - return try .typeDef(.init(syntax: .parseVariant(lexer: &lexer, documents: documents))) + return try .typeDef(.init(syntax: .parseVariant(lexer: &lexer, documents: documents, attributes: attributes))) case .resource: - return try .typeDef(TypeDefSyntax.parseResource(lexer: &lexer, documents: documents)) + return try .typeDef(TypeDefSyntax.parseResource(lexer: &lexer, documents: documents, attributes: attributes)) case .record: - return try .typeDef(.init(syntax: .parseRecord(lexer: &lexer, documents: documents))) + return try .typeDef(.init(syntax: .parseRecord(lexer: &lexer, documents: documents, attributes: attributes))) case .union: - return try .typeDef(.init(syntax: .parseUnion(lexer: &lexer, documents: documents))) + return try .typeDef(.init(syntax: .parseUnion(lexer: &lexer, documents: documents, attributes: attributes))) case .id, .explicitId: return try .function(NamedFunctionSyntax.parse(lexer: &lexer, documents: documents)) case .use: diff --git a/Sources/WIT/TextParser/ParseTop.swift b/Sources/WIT/TextParser/ParseTop.swift index a3674ba3..f4619d1a 100644 --- a/Sources/WIT/TextParser/ParseTop.swift +++ b/Sources/WIT/TextParser/ParseTop.swift @@ -3,6 +3,7 @@ extension SourceFileSyntax { var packageId: PackageNameSyntax? if lexer.peek()?.kind == .package { packageId = try PackageNameSyntax.parse(lexer: &lexer) + try lexer.expectSemicolon() } var items: [ASTItemSyntax] = [] @@ -136,12 +137,24 @@ extension ASTItemSyntax { static func parse( lexer: inout Lexer, documents: DocumentsSyntax ) throws -> ASTItemSyntax { + let attributes = try AttributeSyntax.parseItems(lexer: &lexer) switch lexer.peek()?.kind { case .interface: - return try .interface(InterfaceSyntax.parse(lexer: &lexer, documents: documents)) + return try .interface( + InterfaceSyntax.parse( + lexer: &lexer, documents: documents, attributes: attributes + )) case .world: - return try .world(WorldSyntax.parse(lexer: &lexer, documents: documents)) - case .use: return try .use(.init(syntax: .parse(lexer: &lexer, documents: documents))) + return try .world( + WorldSyntax.parse( + lexer: &lexer, documents: documents, attributes: attributes + )) + case .use: + return try .use( + .init( + syntax: .parse( + lexer: &lexer, documents: documents, attributes: attributes + ))) default: throw ParseError(description: "`world`, `interface` or `use` expected") } @@ -149,14 +162,18 @@ extension ASTItemSyntax { } extension TopLevelUseSyntax { - static func parse(lexer: inout Lexer, documents: DocumentsSyntax) throws -> TopLevelUseSyntax { + static func parse( + lexer: inout Lexer, + documents: DocumentsSyntax, attributes: [AttributeSyntax] + ) throws -> TopLevelUseSyntax { try lexer.expect(.use) let item = try UsePathSyntax.parse(lexer: &lexer) var asName: Identifier? if lexer.eat(.as) { asName = try .parse(lexer: &lexer) } - return TopLevelUseSyntax(item: item, asName: asName) + try lexer.expectSemicolon() + return TopLevelUseSyntax(attributes: attributes, item: item, asName: asName) } } @@ -179,7 +196,8 @@ extension UseSyntax { break } } - return .init(syntax: UseSyntax(from: from, names: names)) + try lexer.expectSemicolon() + return .init(syntax: UseSyntax(attributes: [], from: from, names: names)) } } @@ -222,3 +240,70 @@ extension DocumentsSyntax { return DocumentsSyntax(comments: comments) } } + +extension AttributeSyntax { + static func parseItems(lexer: inout Lexer) throws -> [AttributeSyntax] { + var items: [AttributeSyntax] = [] + while lexer.eat(.at) { + let id = try Identifier.parse(lexer: &lexer) + let item: AttributeSyntax + switch id.text { + case "since": item = .since(try .parse(lexer: &lexer, id: id)) + case "unstable": item = .unstable(try .parse(lexer: &lexer, id: id)) + case "deprecated": item = .deprecated(try .parse(lexer: &lexer, id: id)) + default: + throw ParseError(description: "Unexpected attribute: \(id.text)") + } + items.append(item) + } + return items + } +} + +extension SinceAttributeSyntax { + static func parse(lexer: inout Lexer, id: Identifier) throws -> SinceAttributeSyntax { + try lexer.expect(.leftParen) + try lexer.expectIdentifier("version") + try lexer.expect(.equals) + let version = try Version.parse(lexer: &lexer) + var feature: Identifier? + if lexer.eat(.comma) { + try lexer.expectIdentifier("feature") + try lexer.expect(.equals) + feature = try Identifier.parse(lexer: &lexer) + } + try lexer.expect(.rightParen) + return SinceAttributeSyntax( + version: version, feature: feature, + textRange: id.textRange.lowerBound.. UnstableAttributeSyntax { + try lexer.expect(.leftParen) + try lexer.expectIdentifier("feature") + try lexer.expect(.equals) + let feature = try Identifier.parse(lexer: &lexer) + try lexer.expect(.rightParen) + return UnstableAttributeSyntax( + textRange: id.textRange.lowerBound.. DeprecatedAttributeSyntax { + try lexer.expect(.leftParen) + try lexer.expectIdentifier("version") + try lexer.expect(.equals) + let version = try Version.parse(lexer: &lexer) + try lexer.expect(.rightParen) + return DeprecatedAttributeSyntax( + textRange: id.textRange.lowerBound.. TypeDefSyntax { + static func parse(lexer: inout Lexer, documents: DocumentsSyntax, attributes: [AttributeSyntax]) throws -> TypeDefSyntax { try lexer.expect(.type) let name = try Identifier.parse(lexer: &lexer) try lexer.expect(.equals) let repr = try TypeReprSyntax.parse(lexer: &lexer) - return TypeDefSyntax(documents: documents, name: name, body: .alias(TypeAliasSyntax(typeRepr: repr))) + try lexer.expectSemicolon() + return TypeDefSyntax(documents: documents, attributes: attributes, name: name, body: .alias(TypeAliasSyntax(typeRepr: repr))) } - static func parseFlags(lexer: inout Lexer, documents: DocumentsSyntax) throws -> TypeDefSyntax { + static func parseFlags(lexer: inout Lexer, documents: DocumentsSyntax, attributes: [AttributeSyntax]) throws -> TypeDefSyntax { try lexer.expect(.flags) let name = try Identifier.parse(lexer: &lexer) let body = try TypeDefBodySyntax.flags( @@ -147,24 +148,26 @@ extension TypeDefSyntax { return FlagSyntax(documents: docs, name: name) } )) - return TypeDefSyntax(documents: documents, name: name, body: body) + return TypeDefSyntax(documents: documents, attributes: attributes, name: name, body: body) } - static func parseResource(lexer: inout Lexer, documents: DocumentsSyntax) throws -> SyntaxNode { + static func parseResource(lexer: inout Lexer, documents: DocumentsSyntax, attributes: [AttributeSyntax]) throws -> SyntaxNode { try lexer.expect(.resource) let name = try Identifier.parse(lexer: &lexer) var functions: [ResourceFunctionSyntax] = [] if lexer.eat(.leftBrace) { while !lexer.eat(.rightBrace) { let docs = try DocumentsSyntax.parse(lexer: &lexer) - functions.append(try ResourceFunctionSyntax.parse(lexer: &lexer, documents: docs)) + functions.append(try ResourceFunctionSyntax.parse(lexer: &lexer, documents: docs, attributes: [])) } + } else { + try lexer.expectSemicolon() } let body = TypeDefBodySyntax.resource(ResourceSyntax(functions: functions)) - return .init(syntax: TypeDefSyntax(documents: documents, name: name, body: body)) + return .init(syntax: TypeDefSyntax(documents: documents, attributes: attributes, name: name, body: body)) } - static func parseRecord(lexer: inout Lexer, documents: DocumentsSyntax) throws -> TypeDefSyntax { + static func parseRecord(lexer: inout Lexer, documents: DocumentsSyntax, attributes: [AttributeSyntax]) throws -> TypeDefSyntax { try lexer.expect(.record) let name = try Identifier.parse(lexer: &lexer) let body = try TypeDefBodySyntax.record( @@ -180,10 +183,10 @@ extension TypeDefSyntax { return FieldSyntax(documents: docs, name: name, type: type, textRange: start.. TypeDefSyntax { + static func parseVariant(lexer: inout Lexer, documents: DocumentsSyntax, attributes: [AttributeSyntax]) throws -> TypeDefSyntax { try lexer.expect(.variant) let name = try Identifier.parse(lexer: &lexer) let body = try TypeDefBodySyntax.variant( @@ -203,10 +206,10 @@ extension TypeDefSyntax { }, textRange: name.textRange )) - return TypeDefSyntax(documents: documents, name: name, body: body) + return TypeDefSyntax(documents: documents, attributes: attributes, name: name, body: body) } - static func parseUnion(lexer: inout Lexer, documents: DocumentsSyntax) throws -> TypeDefSyntax { + static func parseUnion(lexer: inout Lexer, documents: DocumentsSyntax, attributes: [AttributeSyntax]) throws -> TypeDefSyntax { try lexer.expect(.union) let name = try Identifier.parse(lexer: &lexer) let body = try TypeDefBodySyntax.union( @@ -221,10 +224,10 @@ extension TypeDefSyntax { }, textRange: name.textRange )) - return TypeDefSyntax(documents: documents, name: name, body: body) + return TypeDefSyntax(documents: documents, attributes: attributes, name: name, body: body) } - static func parseEnum(lexer: inout Lexer, documents: DocumentsSyntax) throws -> TypeDefSyntax { + static func parseEnum(lexer: inout Lexer, documents: DocumentsSyntax, attributes: [AttributeSyntax]) throws -> TypeDefSyntax { try lexer.expect(.enum) let name = try Identifier.parse(lexer: &lexer) let body = try TypeDefBodySyntax.enum( @@ -239,6 +242,6 @@ extension TypeDefSyntax { }, textRange: name.textRange )) - return TypeDefSyntax(documents: documents, name: name, body: body) + return TypeDefSyntax(documents: documents, attributes: attributes, name: name, body: body) } } diff --git a/Sources/WIT/TextParser/ParseWorld.swift b/Sources/WIT/TextParser/ParseWorld.swift index 11083bfd..d325d8e9 100644 --- a/Sources/WIT/TextParser/ParseWorld.swift +++ b/Sources/WIT/TextParser/ParseWorld.swift @@ -1,9 +1,14 @@ extension WorldSyntax { - static func parse(lexer: inout Lexer, documents: DocumentsSyntax) throws -> SyntaxNode { + static func parse( + lexer: inout Lexer, documents: DocumentsSyntax, attributes: [AttributeSyntax] + ) throws -> SyntaxNode { try lexer.expect(.world) let name = try Identifier.parse(lexer: &lexer) let items = try parseItems(lexer: &lexer) - return .init(syntax: WorldSyntax(documents: documents, name: name, items: items)) + return .init( + syntax: WorldSyntax( + documents: documents, attributes: attributes, name: name, items: items + )) } static func parseItems(lexer: inout Lexer) throws -> [WorldItemSyntax] { @@ -14,37 +19,38 @@ extension WorldSyntax { if lexer.eat(.rightBrace) { break } - items.append(try WorldItemSyntax.parse(lexer: &lexer, documents: docs)) + let attributes = try AttributeSyntax.parseItems(lexer: &lexer) + items.append(try WorldItemSyntax.parse(lexer: &lexer, documents: docs, attributes: attributes)) } return items } } extension WorldItemSyntax { - static func parse(lexer: inout Lexer, documents: DocumentsSyntax) throws -> WorldItemSyntax { + static func parse(lexer: inout Lexer, documents: DocumentsSyntax, attributes: [AttributeSyntax]) throws -> WorldItemSyntax { switch lexer.peek()?.kind { case .import: - return try .import(.parse(lexer: &lexer, documents: documents)) + return try .import(.parse(lexer: &lexer, documents: documents, attributes: attributes)) case .export: - return try .export(.parse(lexer: &lexer, documents: documents)) + return try .export(.parse(lexer: &lexer, documents: documents, attributes: attributes)) case .use: return try .use(UseSyntax.parse(lexer: &lexer)) case .type: - return try .type(.init(syntax: .parse(lexer: &lexer, documents: documents))) + return try .type(.init(syntax: .parse(lexer: &lexer, documents: documents, attributes: attributes))) case .flags: - return try .type(.init(syntax: .parseFlags(lexer: &lexer, documents: documents))) + return try .type(.init(syntax: .parseFlags(lexer: &lexer, documents: documents, attributes: attributes))) case .enum: - return try .type(.init(syntax: .parseEnum(lexer: &lexer, documents: documents))) + return try .type(.init(syntax: .parseEnum(lexer: &lexer, documents: documents, attributes: attributes))) case .variant: - return try .type(.init(syntax: .parseVariant(lexer: &lexer, documents: documents))) + return try .type(.init(syntax: .parseVariant(lexer: &lexer, documents: documents, attributes: attributes))) case .resource: - return try .type(TypeDefSyntax.parseResource(lexer: &lexer, documents: documents)) + return try .type(TypeDefSyntax.parseResource(lexer: &lexer, documents: documents, attributes: attributes)) case .record: - return try .type(.init(syntax: .parseRecord(lexer: &lexer, documents: documents))) + return try .type(.init(syntax: .parseRecord(lexer: &lexer, documents: documents, attributes: attributes))) case .union: - return try .type(.init(syntax: .parseUnion(lexer: &lexer, documents: documents))) + return try .type(.init(syntax: .parseUnion(lexer: &lexer, documents: documents, attributes: attributes))) case .include: - return try .include(.parse(lexer: &lexer)) + return try .include(.parse(lexer: &lexer, attributes: attributes)) default: throw ParseError(description: "`type`, `resource` or `func` expected") } @@ -52,18 +58,22 @@ extension WorldItemSyntax { } extension ImportSyntax { - static func parse(lexer: inout Lexer, documents: DocumentsSyntax) throws -> ImportSyntax { + static func parse( + lexer: inout Lexer, + documents: DocumentsSyntax, + attributes: [AttributeSyntax] + ) throws -> ImportSyntax { try lexer.expect(.import) let kind = try ExternKindSyntax.parse(lexer: &lexer) - return ImportSyntax(documents: documents, kind: kind) + return ImportSyntax(documents: documents, attributes: attributes, kind: kind) } } extension ExportSyntax { - static func parse(lexer: inout Lexer, documents: DocumentsSyntax) throws -> ExportSyntax { + static func parse(lexer: inout Lexer, documents: DocumentsSyntax, attributes: [AttributeSyntax]) throws -> ExportSyntax { try lexer.expect(.export) let kind = try ExternKindSyntax.parse(lexer: &lexer) - return ExportSyntax(documents: documents, kind: kind) + return ExportSyntax(documents: documents, attributes: attributes, kind: kind) } } @@ -74,9 +84,11 @@ extension ExternKindSyntax { if clone.eat(.colon) { switch clone.peek()?.kind { case .func: - // import foo: func(...) + // import foo: func(...); lexer = clone - return try .function(id, .parse(lexer: &lexer)) + let result: ExternKindSyntax = try .function(id, .parse(lexer: &lexer)) + try lexer.expectSemicolon() + return result case .interface: // import foo: interface { ... } try clone.expect(.interface) @@ -85,13 +97,15 @@ extension ExternKindSyntax { default: break } } - // import foo:bar/baz - return try .path(.parse(lexer: &lexer)) + // import foo:bar/baz; + let result: ExternKindSyntax = try .path(.parse(lexer: &lexer)) + try lexer.expectSemicolon() + return result } } extension IncludeSyntax { - static func parse(lexer: inout Lexer) throws -> IncludeSyntax { + static func parse(lexer: inout Lexer, attributes: [AttributeSyntax]) throws -> IncludeSyntax { try lexer.expect(.include) let from = try UsePathSyntax.parse(lexer: &lexer) @@ -104,6 +118,7 @@ extension IncludeSyntax { return IncludeNameSyntax(name: name, asName: asName) } } - return IncludeSyntax(from: from, names: names) + try lexer.expectSemicolon() + return IncludeSyntax(attributes: attributes, from: from, names: names) } } diff --git a/Tests/WITTests/TextParser/ParseFunctionDeclTests.swift b/Tests/WITTests/TextParser/ParseFunctionDeclTests.swift index 671c5443..e8f7c1cd 100644 --- a/Tests/WITTests/TextParser/ParseFunctionDeclTests.swift +++ b/Tests/WITTests/TextParser/ParseFunctionDeclTests.swift @@ -8,7 +8,8 @@ class ParseFunctionDeclTests: XCTestCase { var lexer = Lexer(cursor: .init(input: text)) return try ResourceFunctionSyntax.parse( lexer: &lexer, - documents: DocumentsSyntax(comments: []) + documents: DocumentsSyntax(comments: []), + attributes: [] ) } diff --git a/Tests/WITTests/TextParser/ParseInterfaceTests.swift b/Tests/WITTests/TextParser/ParseInterfaceTests.swift index 5bf22dd3..9dcb7ceb 100644 --- a/Tests/WITTests/TextParser/ParseInterfaceTests.swift +++ b/Tests/WITTests/TextParser/ParseInterfaceTests.swift @@ -8,7 +8,7 @@ class ParseInterfaceTests: XCTestCase { var lexer = Lexer(cursor: .init(input: text)) return try InterfaceSyntax.parse( lexer: &lexer, - documents: DocumentsSyntax(comments: []) + documents: DocumentsSyntax(comments: []), attributes: [] ) } diff --git a/Tests/WITTests/TextParser/ParseTopTests.swift b/Tests/WITTests/TextParser/ParseTopTests.swift index 34e56408..b6642e4f 100644 --- a/Tests/WITTests/TextParser/ParseTopTests.swift +++ b/Tests/WITTests/TextParser/ParseTopTests.swift @@ -109,7 +109,7 @@ class ParseTopTests: XCTestCase { func testTopLevelUseAs() throws { var lexer = Lexer(cursor: .init(input: "use abc as xyz")) - let use = try TopLevelUseSyntax.parse(lexer: &lexer, documents: .init(comments: [])) + let use = try TopLevelUseSyntax.parse(lexer: &lexer, documents: .init(comments: []), attributes: []) XCTAssertEqual(use.item.name.text, "abc") XCTAssertEqual(use.asName?.text, "xyz") } @@ -132,7 +132,7 @@ class ParseTopTests: XCTestCase { func testUsePath() throws { var lexer = Lexer(cursor: .init(input: "use ns1:pkg1/item1@1.0.0")) - let use = try TopLevelUseSyntax.parse(lexer: &lexer, documents: .init(comments: [])) + let use = try TopLevelUseSyntax.parse(lexer: &lexer, documents: .init(comments: []), attributes: []) XCTAssertEqual(use.item.name.text, "item1") guard case let .package(id, _) = use.item else { XCTFail("expected package but got \(use.item)") @@ -141,6 +141,59 @@ class ParseTopTests: XCTestCase { XCTAssertEqual(id.namespace.text, "ns1") XCTAssertEqual(id.name.text, "pkg1") } + + func testAttributeSince() throws { + var lexer = Lexer( + cursor: .init( + input: """ + @since(version = 1.0.0) + @since(version = 1.0.0, feature = foo-bar) + """ + )) + let attributes = try AttributeSyntax.parseItems(lexer: &lexer) + guard attributes.count == 2 else { + XCTFail("expected 2 attributes but got \(attributes)") + return + } + do { + guard case let .since(attribute) = attributes[0] else { + XCTFail("expected since but got \(attributes[0])") + return + } + XCTAssertEqual(attribute.version.description, "1.0.0") + XCTAssertEqual(attribute.feature?.text, nil) + } + do { + guard case let .since(attribute) = attributes[1] else { + XCTFail("expected since but got \(attributes[1])") + return + } + XCTAssertEqual(attribute.version.description, "1.0.0") + XCTAssertEqual(attribute.feature?.text, "foo-bar") + } + } + + func testAttributeUnstable() throws { + var lexer = Lexer(cursor: .init(input: "@unstable(feature = foo)")) + let attributes = try AttributeSyntax.parseItems(lexer: &lexer) + XCTAssertEqual(attributes.count, 1) + guard case let .unstable(attribute) = attributes.first else { + XCTFail("expected since but got \(attributes)") + return + } + XCTAssertEqual(attribute.feature.text, "foo") + } + + func testAttributeDeprecated() throws { + var lexer = Lexer(cursor: .init(input: "@deprecated(version = 1.0.3)")) + let attributes = try AttributeSyntax.parseItems(lexer: &lexer) + XCTAssertEqual(attributes.count, 1) + guard case let .deprecated(attribute) = attributes.first else { + XCTFail("expected since but got \(attributes)") + return + } + XCTAssertEqual(attribute.version.description, "1.0.3") + } } extension SourceFileSyntax { diff --git a/Tests/WITTests/TextParser/ParseTypesTests.swift b/Tests/WITTests/TextParser/ParseTypesTests.swift index 85336e92..e8b2272a 100644 --- a/Tests/WITTests/TextParser/ParseTypesTests.swift +++ b/Tests/WITTests/TextParser/ParseTypesTests.swift @@ -48,7 +48,7 @@ class ParseTestsTests: XCTestCase { """ ) ) - let interface = try InterfaceSyntax.parse(lexer: &lexer, documents: .init(comments: [])) + let interface = try InterfaceSyntax.parse(lexer: &lexer, documents: .init(comments: []), attributes: []) XCTAssertEqual(interface.items.count, 30) } @@ -72,7 +72,7 @@ class ParseTestsTests: XCTestCase { """ ) ) - let typeDef = try TypeDefSyntax.parseResource(lexer: &lexer, documents: .init(comments: [])) + let typeDef = try TypeDefSyntax.parseResource(lexer: &lexer, documents: .init(comments: []), attributes: []) XCTAssertEqual(typeDef.name.text, "r1") guard case let .resource(resource) = typeDef.body else { XCTFail("unexpected type kind: \(typeDef.body)") @@ -93,7 +93,7 @@ class ParseTestsTests: XCTestCase { """ ) ) - let typeDef = try TypeDefSyntax.parseVariant(lexer: &lexer, documents: .init(comments: [])) + let typeDef = try TypeDefSyntax.parseVariant(lexer: &lexer, documents: .init(comments: []), attributes: []) XCTAssertEqual(typeDef.name.text, "r1") guard case let .variant(variant) = typeDef.body else { XCTFail("unexpected type kind: \(typeDef.body)") diff --git a/Tests/WITTests/TextParser/ParseWorldTests.swift b/Tests/WITTests/TextParser/ParseWorldTests.swift index e19d519d..689345c8 100644 --- a/Tests/WITTests/TextParser/ParseWorldTests.swift +++ b/Tests/WITTests/TextParser/ParseWorldTests.swift @@ -8,7 +8,8 @@ final class ParseWorldTests: XCTestCase { var lexer = Lexer(cursor: .init(input: text)) return try WorldSyntax.parse( lexer: &lexer, - documents: DocumentsSyntax(comments: []) + documents: DocumentsSyntax(comments: []), + attributes: [] ) }