From 78bd8056924943b687207611756454f40e7692c8 Mon Sep 17 00:00:00 2001 From: Miles Ziemer <45497130+milesziemer@users.noreply.github.com> Date: Fri, 16 Jun 2023 09:20:57 -0400 Subject: [PATCH] Add parse failure tests (#1820) * Add parse failure tests Adds test cases that validate the parser fails when expected by asserting that any descendant of the root is an error tree. There is a test per-production, if that production has terminal tokens, or represents an alternation of productions that are distinguished by terminal tokens. For example, SHAPE_OR_APPLY_STATEMENT chooses between shape and apply statements based on whether the current lexemme is "apply". Only cases where the terminal tokens are invalid are tested because if a non-terminal (ie. another production) is invalid, the error tree is a child of the tree for that production. Since we also have tests that make sure the correct children are present for each production, we should have enough coverage to only test invalid non-terminals. For example, when parsing this shape statement: ``` structure Foo.Bar {} ``` we know the `Foo.Bar` is part of the IDENTIFIER production, we have tests that validate the the IDENTIFIER production is present, and we have tests that check for errors in IDENTIFIERs, so we don't need to specifically test this kind of error for shape statements. One caveat is that the existing tests I'm referring to only ensure all expected children are present for valid trees, so there is a logical inconsistency in the argument that these tests provide full coverage, but I can't think of a case that could cause a problem. Either way, these tests should provide _good enough_ coverage. Some bug fixes: - Fixed an issue where the error caused by not finding a "namespace" in NAMESPACE_STATEMENT was recovered by the parent tree. - Expect IDENTIFIER for "with" token in MIXINS production. * Add cases for productions with loops Adds more test cases for `*Statement` productions to ensure loops are broken out of properly when parsing fails. --- .../amazon/smithy/syntax/TreeType.java | 18 +- .../amazon/smithy/syntax/TreeTypeTest.java | 538 ++++++++++++++++++ 2 files changed, 547 insertions(+), 9 deletions(-) diff --git a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/TreeType.java b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/TreeType.java index 634b85ade32..e305740235b 100644 --- a/smithy-syntax/src/main/java/software/amazon/smithy/syntax/TreeType.java +++ b/smithy-syntax/src/main/java/software/amazon/smithy/syntax/TreeType.java @@ -116,19 +116,19 @@ void parse(CapturingTokenizer tokenizer) { NAMESPACE_STATEMENT { @Override void parse(CapturingTokenizer tokenizer) { - if (tokenizer.isCurrentLexeme("namespace")) { - tokenizer.withState(this, () -> { + tokenizer.withState(this, () -> { + if (tokenizer.isCurrentLexeme("namespace")) { tokenizer.next(); // skip "namespace" SP.parse(tokenizer); NAMESPACE.parse(tokenizer); BR.parse(tokenizer); - }); - } else if (tokenizer.hasNext()) { - throw new ModelSyntaxException( - "Expected a namespace definition but found " - + tokenizer.getCurrentToken().getDebug(tokenizer.getCurrentTokenLexeme()), - tokenizer.getCurrentTokenLocation()); - } + } else if (tokenizer.hasNext()) { + throw new ModelSyntaxException( + "Expected a namespace definition but found " + + tokenizer.getCurrentToken().getDebug(tokenizer.getCurrentTokenLexeme()), + tokenizer.getCurrentTokenLocation()); + } + }); } }, diff --git a/smithy-syntax/src/test/java/software/amazon/smithy/syntax/TreeTypeTest.java b/smithy-syntax/src/test/java/software/amazon/smithy/syntax/TreeTypeTest.java index 2a73cec25f4..def758ddff9 100644 --- a/smithy-syntax/src/test/java/software/amazon/smithy/syntax/TreeTypeTest.java +++ b/smithy-syntax/src/test/java/software/amazon/smithy/syntax/TreeTypeTest.java @@ -1301,7 +1301,538 @@ public void textBlock() { TokenTree withQuotesTree = getTree(TreeType.TEXT_BLOCK, withQuotes); assertTreeIsValid(withQuotesTree); rootAndChildTypesEqual(withQuotesTree, TreeType.TEXT_BLOCK, TreeType.TOKEN); + } + + @Test + public void invalidControlSection() { + String invalidTrailing = "$foo: bar\n$"; + TokenTree invalidTrailingTree = getTree(TreeType.CONTROL_SECTION, invalidTrailing); + assertTreeIsInvalid(invalidTrailingTree); + + String invalidLeading = "$foo: \nbar\n$foo: bar\n"; + TokenTree invalidLeadingTree = getTree(TreeType.CONTROL_SECTION, invalidLeading); + assertTreeIsInvalid(invalidLeadingTree); + + String multipleInvalid = "$foo: bar\n$1\n#foo: bar\n$foo = bar\n$foo: bar\n"; + TokenTree multipleInvalidTree = getTree(TreeType.CONTROL_SECTION, multipleInvalid); + assertTreeIsInvalid(multipleInvalidTree); + } + + @Test + public void invalidControlStatement() { + String missingDollar = "version: 2\n"; + TokenTree missingDollarTree = getTree(TreeType.CONTROL_STATEMENT, missingDollar); + assertTreeIsInvalid(missingDollarTree); + + String missingColon = "$version 2\n"; + TokenTree missingColonTree = getTree(TreeType.CONTROL_STATEMENT, missingColon); + assertTreeIsInvalid(missingColonTree); + + String notDollar = "=version: 2\n"; + TokenTree notDollarTree = getTree(TreeType.CONTROL_STATEMENT, notDollar); + assertTreeIsInvalid(notDollarTree); + + String notColon = "$version = 2\n"; + TokenTree notColonTree = getTree(TreeType.CONTROL_STATEMENT, notColon); + assertTreeIsInvalid(notColonTree); + } + + @Test + public void invalidMetadataSection() { + String invalidTrailing = "metadata foo = bar\nmetadata = bar\n"; + TokenTree invalidTrailingTree = getTree(TreeType.METADATA_SECTION, invalidTrailing); + assertTreeIsInvalid(invalidTrailingTree); + + String invalidLeading = "metadata foo =\nbar\nmetadata = bar\n"; + TokenTree invalidLeadingTree = getTree(TreeType.METADATA_SECTION, invalidLeading); + assertTreeIsInvalid(invalidLeadingTree); + + String multipleInvalid = "metadata foo = bar\nmetadata = \nfoo\nmetadata bar: baz\nmetadata baz = qux\n"; + TokenTree multipleInvalidTree = getTree(TreeType.METADATA_SECTION, multipleInvalid); + assertTreeIsInvalid(multipleInvalidTree); + } + + @Test + public void invalidMetadataStatement() { + String missingKeyword = "foo = bar\n"; + TokenTree missingKeywordTree = getTree(TreeType.METADATA_STATEMENT, missingKeyword); + assertTreeIsInvalid(missingKeywordTree); + + String missingEquals = "metadata foo bar\n"; + TokenTree missingEqualsTree = getTree(TreeType.METADATA_STATEMENT, missingEquals); + assertTreeIsInvalid(missingEqualsTree); + + String notEquals = "metadata foo: bar\n"; + TokenTree notEqualsTree = getTree(TreeType.METADATA_STATEMENT, notEquals); + assertTreeIsInvalid(notEqualsTree); + } + + @Test + public void invalidNodeValue() { + String notNodeValue = "$"; + TokenTree tree = getTree(TreeType.NODE_VALUE, notNodeValue); + assertTreeIsInvalid(tree); + } + + @Test + public void invalidNodeArray() { + String missingOpenBracket = "foo, bar]"; + TokenTree missingOpenBracketTree = getTree(TreeType.NODE_ARRAY, missingOpenBracket); + assertTreeIsInvalid(missingOpenBracketTree); + + String missingCloseBracket = "[foo, bar"; + TokenTree missingCloseBracketTree = getTree(TreeType.NODE_ARRAY, missingCloseBracket); + assertTreeIsInvalid(missingCloseBracketTree); + + String missingBrackets = "foo, bar"; + TokenTree missingBracketsTree = getTree(TreeType.NODE_OBJECT, missingBrackets); + assertTreeIsInvalid(missingBracketsTree); + + String notBrackets = "{foo, bar}"; + TokenTree notBracketsTree = getTree(TreeType.NODE_OBJECT, notBrackets); + assertTreeIsInvalid(notBracketsTree); + } + + @Test + public void invalidNodeObject() { + String missingOpenBrace = "foo: bar}"; + TokenTree missingOpenBraceTree = getTree(TreeType.NODE_OBJECT, missingOpenBrace); + assertTreeIsInvalid(missingOpenBraceTree); + + String missingCloseBrace = "{foo: bar"; + TokenTree missingCloseBraceTree = getTree(TreeType.NODE_OBJECT, missingCloseBrace); + assertTreeIsInvalid(missingCloseBraceTree); + + String missingBraces = "foo: bar"; + TokenTree missingBracesTree = getTree(TreeType.NODE_OBJECT, missingBraces); + assertTreeIsInvalid(missingBracesTree); + + String notBraces = "[foo: bar]"; + TokenTree notBracesTree = getTree(TreeType.NODE_ARRAY, notBraces); + assertTreeIsInvalid(notBracesTree); + } + + @Test + public void invalidNodeObjectKvp() { + String notColon = "foo = bar"; + TokenTree notColonTree = getTree(TreeType.NODE_OBJECT_KVP, notColon); + assertTreeIsInvalid(notColonTree); + + String missingColon = "foo bar"; + TokenTree missingColonTree = getTree(TreeType.NODE_OBJECT_KVP, missingColon); + assertTreeIsInvalid(missingColonTree); + } + + @Test + public void invalidNodeObjectKey() { + String invalid = "1"; + TokenTree tree = getTree(TreeType.NODE_OBJECT_KEY, invalid); + assertTreeIsInvalid(tree); + } + + @Test + public void invalidNamespaceStatement() { + String missingKeyword = " foo.bar\n"; + TokenTree missingKeywordTree = getTree(TreeType.NAMESPACE_STATEMENT, missingKeyword); + assertTreeIsInvalid(missingKeywordTree); + } + + @Test + public void invalidUseSection() { + String invalidTrailing = "use com.foo#Bar\nuse \ncom.foo#Bar\n"; + TokenTree invalidTrailingTree = getTree(TreeType.USE_SECTION, invalidTrailing); + assertTreeIsInvalid(invalidTrailingTree); + + String invalidLeading = "use\n com.foo#Bar\nuse com.foo#Bar\n"; + TokenTree invalidLeadingTree = getTree(TreeType.USE_SECTION, invalidLeading); + assertTreeIsInvalid(invalidLeadingTree); + + String multipleInvalid = "use com.foo#Bar\nuse\ncom.foo#Bar\nuse #Bar\nuse com.foo#Bar\n"; + TokenTree multipleInvalidTree = getTree(TreeType.USE_SECTION, multipleInvalid); + assertTreeIsInvalid(multipleInvalidTree); + } + + @Test + public void invalidUseStatement() { + String missingKeyword = " foo.bar#Baz\n"; + TokenTree missingKeywordTree = getTree(TreeType.USE_STATEMENT, missingKeyword); + assertTreeIsInvalid(missingKeywordTree); + } + + @Test + public void invalidShapeStatements() { + String incompleteShape = "string Foo\nstructure Bar {\nfoo: Foo\n"; + TokenTree incompleteShapeTree = getTree(TreeType.SHAPE_STATEMENTS, incompleteShape); + assertTreeIsInvalid(incompleteShapeTree); + + String firstInvalid = "string Foo {}\nstructure Bar{}\n"; + TokenTree firstInvalidTree = getTree(TreeType.SHAPE_STATEMENTS, firstInvalid); + assertTreeIsInvalid(firstInvalidTree); + + String trailingTraits = "structure Foo {}\n@bar\n@baz(foo: bar)\n"; + TokenTree trailingTraitsTree = getTree(TreeType.SHAPE_STATEMENTS, trailingTraits); + assertTreeIsInvalid(trailingTraitsTree); + } + + @Test + public void invalidShapeOrApplyStatement() { + // SHAPE_OR_APPLY_STATEMENT checks whether it's an apply or shape statement, + // and shape types are checked before that type is parsed, so this test + // covers all invalid shape type name/apply cases. + + String missingTypeName = " Foo with [Bar] {}"; + TokenTree missingTypeNameTree = getTree(TreeType.SHAPE_STATEMENT, missingTypeName); + assertTreeIsInvalid(missingTypeNameTree); + + String wrongTypeName = "unknown Foo with [Bar] {}"; + TokenTree wrongTypeNameTree = getTree(TreeType.SHAPE_STATEMENT, wrongTypeName); + assertTreeIsInvalid(wrongTypeNameTree); + } + @Test + public void invalidMixins() { + String missingOpenBracket = "with foo, bar]"; + TokenTree missingOpenBracketTree = getTree(TreeType.MIXINS, missingOpenBracket); + assertTreeIsInvalid(missingOpenBracketTree); + + String missingCloseBracket = "with [foo, bar"; + TokenTree missingCloseBracketTree = getTree(TreeType.MIXINS, missingCloseBracket); + assertTreeIsInvalid(missingCloseBracketTree); + + String missingBrackets = "with foo, bar"; + TokenTree missingBracketsTree = getTree(TreeType.MIXINS, missingBrackets); + assertTreeIsInvalid(missingBracketsTree); + + String notBrackets = "with {foo, bar}"; + TokenTree notBracketsTree = getTree(TreeType.MIXINS, notBrackets); + assertTreeIsInvalid(notBracketsTree); + } + + @Test + public void invalidEnumShapeMembers() { + String missingOpenBrace = "FOO }"; + TokenTree missingOpenBraceTree = getTree(TreeType.ENUM_SHAPE_MEMBERS, missingOpenBrace); + assertTreeIsInvalid(missingOpenBraceTree); + + String missingCloseBrace = "{FOO"; + TokenTree missingCloseBraceTree = getTree(TreeType.ENUM_SHAPE_MEMBERS, missingCloseBrace); + assertTreeIsInvalid(missingCloseBraceTree); + + String missingBraces = "FOO"; + TokenTree missingBracesTree = getTree(TreeType.ENUM_SHAPE_MEMBERS, missingBraces); + assertTreeIsInvalid(missingBracesTree); + + String notBraces = "[FOO]"; + TokenTree notBracesTree = getTree(TreeType.ENUM_SHAPE_MEMBERS, notBraces); + assertTreeIsInvalid(notBracesTree); + + String leadingInvalidMember = "{\n1\nFOO\n}"; + TokenTree leadingInvalidMemberTree = getTree(TreeType.ENUM_SHAPE_MEMBERS, leadingInvalidMember); + assertTreeIsInvalid(leadingInvalidMemberTree); + + String trailingInvalidMember = "{\nFOO\n1\n}"; + TokenTree trailingInvalidMemberTree = getTree(TreeType.ENUM_SHAPE_MEMBERS, trailingInvalidMember); + assertTreeIsInvalid(trailingInvalidMemberTree); + + String multipleInvalidMembers = "{\nFOO\nBAR = \n @foo(\nBAR\n = 2\nFOO\n}"; + TokenTree multipleInvalidMembersTree = getTree(TreeType.ENUM_SHAPE_MEMBERS, multipleInvalidMembers); + assertTreeIsInvalid(multipleInvalidMembersTree); + + String trailingTraits = "{\nFOO\n@bar\n@baz\n}"; + TokenTree trailingTraitsTree = getTree(TreeType.ENUM_SHAPE_MEMBERS, trailingTraits); + assertTreeIsInvalid(trailingTraitsTree); + } + + @Test + public void invalidValueAssignment() { + String missingEquals = "1\n"; + TokenTree missingEqualsTree = getTree(TreeType.VALUE_ASSIGNMENT, missingEquals); + assertTreeIsInvalid(missingEqualsTree); + + String notEquals = " + 1\n"; + TokenTree notEqualsTree = getTree(TreeType.VALUE_ASSIGNMENT, notEquals); + assertTreeIsInvalid(notEqualsTree); + } + + @Test + public void invalidForResource() { + String missingFor = " foo.bar#Baz"; + TokenTree missingForTree = getTree(TreeType.FOR_RESOURCE, missingFor); + assertTreeIsInvalid(missingForTree); + } + + @Test + public void invalidShapeMembers() { + String missingOpenBrace = "foo: bar }"; + TokenTree missingOpenBraceTree = getTree(TreeType.SHAPE_MEMBERS, missingOpenBrace); + assertTreeIsInvalid(missingOpenBraceTree); + + String missingCloseBrace = "{foo: bar"; + TokenTree missingCloseBraceTree = getTree(TreeType.SHAPE_MEMBERS, missingCloseBrace); + assertTreeIsInvalid(missingCloseBraceTree); + String missingBraces = "foo: bar"; + TokenTree missingBracesTree = getTree(TreeType.SHAPE_MEMBERS, missingBraces); + assertTreeIsInvalid(missingBracesTree); + + String notBraces = "[foo: bar]"; + TokenTree notBracesTree = getTree(TreeType.SHAPE_MEMBERS, notBraces); + assertTreeIsInvalid(notBracesTree); + + String leadingInvalidMember = "{\nfoo: 1\nbar: baz\n}"; + TokenTree leadingInvalidMemberTree = getTree(TreeType.SHAPE_MEMBERS, leadingInvalidMember); + assertTreeIsInvalid(leadingInvalidMemberTree); + + String trailingInvalidMember = "{\nfoo: bar\nbaz:\n}"; + TokenTree trailingInvalidMemberTree = getTree(TreeType.SHAPE_MEMBERS, trailingInvalidMember); + assertTreeIsInvalid(trailingInvalidMemberTree); + + String multipleInvalidMembers = "{\nfoo: @foo({})\nfoo = com.foo#Bar\n}"; + TokenTree multipleInvalidMembersTree = getTree(TreeType.SHAPE_MEMBERS, multipleInvalidMembers); + assertTreeIsInvalid(multipleInvalidMembersTree); + + String trailingTraits = "{\nfoo: bar\n@foo\n@bar\n}"; + TokenTree trailingTraitsTree = getTree(TreeType.SHAPE_MEMBERS, trailingTraits); + assertTreeIsInvalid(trailingTraitsTree); + } + + @Test + public void invalidExplicitShapeMember() { + String missingColon = "foo Bar"; + TokenTree missingColonTree = getTree(TreeType.EXPLICIT_SHAPE_MEMBER, missingColon); + assertTreeIsInvalid(missingColonTree); + + String notColon = "foo = Bar"; + TokenTree notColonTree = getTree(TreeType.EXPLICIT_SHAPE_MEMBER, notColon); + assertTreeIsInvalid(notColonTree); + } + + @Test + public void invalidElidedShapeMember() { + String missingDollar = "Foo"; + TokenTree missingDollarTree = getTree(TreeType.ELIDED_SHAPE_MEMBER, missingDollar); + assertTreeIsInvalid(missingDollarTree); + + String notDollar = "#Foo"; + TokenTree notDollarTree = getTree(TreeType.ELIDED_SHAPE_MEMBER, notDollar); + assertTreeIsInvalid(notDollarTree); + } + + @Test + public void invalidOperationProperty() { + // This production determines which kind of operation property is present, + // so these cases cover unexpected/invalid property names. + + String missingPropertyName = " : FooInput"; + TokenTree missingPropertyNameTree = getTree(TreeType.OPERATION_PROPERTY, missingPropertyName); + assertTreeIsInvalid(missingPropertyNameTree); + + String wrongPropertyName = "unknown: FooInput"; + TokenTree wrongPropertyNameTree = getTree(TreeType.OPERATION_PROPERTY, wrongPropertyName); + assertTreeIsInvalid(wrongPropertyNameTree); + } + + @Test + public void invalidOperationInput() { + String missingColon = "input Foo"; + TokenTree missingColonTree = getTree(TreeType.OPERATION_INPUT, missingColon); + assertTreeIsInvalid(missingColonTree); + + String notColon = "input = Foo"; + TokenTree notColonTree = getTree(TreeType.OPERATION_INPUT, notColon); + assertTreeIsInvalid(notColonTree); + + String missingValue = "input: "; + TokenTree missingValueTree = getTree(TreeType.OPERATION_INPUT, missingValue); + assertTreeIsInvalid(missingValueTree); + } + + @Test + public void invalidOperationOutput() { + String missingColon = "output Foo"; + TokenTree missingColonTree = getTree(TreeType.OPERATION_OUTPUT, missingColon); + assertTreeIsInvalid(missingColonTree); + + String notColon = "output = Foo"; + TokenTree notColonTree = getTree(TreeType.OPERATION_OUTPUT, notColon); + assertTreeIsInvalid(notColonTree); + + String missingValue = "output: "; + TokenTree missingValueTree = getTree(TreeType.OPERATION_OUTPUT, missingValue); + assertTreeIsInvalid(missingValueTree); + } + + @Test + public void invalidOperationErrors() { + String missingColon = "errors Foo"; + TokenTree missingColonTree = getTree(TreeType.OPERATION_ERRORS, missingColon); + assertTreeIsInvalid(missingColonTree); + + + String notColon = "errors = Foo"; + TokenTree notColonTree = getTree(TreeType.OPERATION_ERRORS, notColon); + assertTreeIsInvalid(notColonTree); + + String missingValue = "errors: "; + TokenTree missingValueTree = getTree(TreeType.OPERATION_ERRORS, missingValue); + assertTreeIsInvalid(missingValueTree); + + String missingOpenBracket = "errors: Foo, Bar]"; + TokenTree missingOpenBracketTree = getTree(TreeType.OPERATION_ERRORS, missingOpenBracket); + assertTreeIsInvalid(missingOpenBracketTree); + + String missingCloseBracket = "errors: [Foo, Bar"; + TokenTree missingCloseBracketTree = getTree(TreeType.OPERATION_ERRORS, missingCloseBracket); + assertTreeIsInvalid(missingCloseBracketTree); + + String missingBrackets = "errors: Foo, Bar"; + TokenTree missingBracketsTree = getTree(TreeType.OPERATION_ERRORS, missingBrackets); + assertTreeIsInvalid(missingBracketsTree); + + String notBrackets = "errors: {Foo, Bar}"; + TokenTree notBracketsTree = getTree(TreeType.OPERATION_ERRORS, notBrackets); + assertTreeIsInvalid(notBracketsTree); + } + + @Test + public void invalidInlineAggregateShape() { + String missingWalrus = " @foo\n for Bar with [Baz] {}"; + TokenTree missingWalrusTree = getTree(TreeType.INLINE_AGGREGATE_SHAPE, missingWalrus); + assertTreeIsInvalid(missingWalrusTree); + + String notWalrus = "= @foo\n for bar with [Baz] {}"; + TokenTree notWalrusTree = getTree(TreeType.INLINE_AGGREGATE_SHAPE, notWalrus); + assertTreeIsInvalid(notWalrusTree); + } + + @Test + public void invalidTraitStatements() { + String incomplete = "@foo({bar: baz}\n"; + TokenTree incompleteTree = getTree(TreeType.TRAIT_STATEMENTS, incomplete); + assertTreeIsInvalid(incompleteTree); + + String leadingInvalid = "@foo(:)\n@bar\n"; + TokenTree leadingInvalidTree = getTree(TreeType.TRAIT_STATEMENTS, leadingInvalid); + assertTreeIsInvalid(leadingInvalidTree); + + String trailingInvalid = "@foo\n@bar(\n"; + TokenTree trailingInvalidTree = getTree(TreeType.TRAIT_STATEMENTS, trailingInvalid); + assertTreeIsInvalid(trailingInvalidTree); + + String multipleInvalid = "@foo\n@\nbaz\n@foo({\n@bar\n"; + TokenTree multipleInvalidTree = getTree(TreeType.TRAIT_STATEMENTS, multipleInvalid); + assertTreeIsInvalid(multipleInvalidTree); + } + + @Test + public void invalidTrait() { + String missingAt = "foo(bar: baz)"; + TokenTree missingAtTree = getTree(TreeType.TRAIT, missingAt); + assertTreeIsInvalid(missingAtTree); + } + + @Test + public void invalidTraitBody() { + String missingOpenParen = "foo: bar)"; + TokenTree missingOpenParenTree = getTree(TreeType.TRAIT_BODY, missingOpenParen); + assertTreeIsInvalid(missingOpenParenTree); + + String missingCloseParen = "(foo: bar"; + TokenTree missingCloseParenTree = getTree(TreeType.TRAIT_BODY, missingCloseParen); + assertTreeIsInvalid(missingCloseParenTree); + + String missingParens = "foo: bar"; + TokenTree missingParensTree = getTree(TreeType.TRAIT_BODY, missingParens); + assertTreeIsInvalid(missingParensTree); + + String notParens = "{foo: bar}"; + TokenTree notParensTree = getTree(TreeType.TRAIT_BODY, notParens); + assertTreeIsInvalid(notParensTree); + } + + @Test + public void invalidApplyStatementBlock() { + String missingOpenBrace = "apply foo @bar }"; + TokenTree missingOpenBraceTree = getTree(TreeType.APPLY_STATEMENT_BLOCK, missingOpenBrace); + assertTreeIsInvalid(missingOpenBraceTree); + + String missingCloseBrace = "apply foo { @bar"; + TokenTree missingCloseBraceTree = getTree(TreeType.APPLY_STATEMENT_BLOCK, missingCloseBrace); + assertTreeIsInvalid(missingCloseBraceTree); + + String missingBraces = "apply foo @bar"; + TokenTree missingBracesTree = getTree(TreeType.APPLY_STATEMENT_BLOCK, missingBraces); + assertTreeIsInvalid(missingBracesTree); + + String notBraces = "apply foo [@bar]"; + TokenTree notBracesTree = getTree(TreeType.APPLY_STATEMENT_BLOCK, notBraces); + assertTreeIsInvalid(notBracesTree); + + String invalidTraits = "apply foo {\n@bar(\n@ baz\n}"; + TokenTree invalidTraitsTree = getTree(TreeType.APPLY_STATEMENT_BLOCK, invalidTraits); + assertTreeIsInvalid(invalidTraitsTree); + } + + @Test + public void invalidAbsoluteRootShapeId() { + String notPound = "com.foo$Bar"; + TokenTree notPoundTree = getTree(TreeType.ABSOLUTE_ROOT_SHAPE_ID, notPound); + assertTreeIsInvalid(notPoundTree); + + String trailingPound = "com.foo#"; + TokenTree trailingPoundTree = getTree(TreeType.ABSOLUTE_ROOT_SHAPE_ID, trailingPound); + assertTreeIsInvalid(trailingPoundTree); + + String multiPound = "com.foo##Bar"; + TokenTree multiPoundTree = getTree(TreeType.ABSOLUTE_ROOT_SHAPE_ID, multiPound); + assertTreeIsInvalid(multiPoundTree); + } + + @Test + public void invalidShapeIdMember() { + String missingIdentifier = "$"; + TokenTree missingIdentifierTree = getTree(TreeType.SHAPE_ID_MEMBER, missingIdentifier); + assertTreeIsInvalid(missingIdentifierTree); + + String notDollar = "#Foo"; + TokenTree notDollarTree = getTree(TreeType.SHAPE_ID_MEMBER, notDollar); + assertTreeIsInvalid(notDollarTree); + } + + @Test + public void invalidNamespace() { + String trailingDot = "com.foo."; + TokenTree trailingDotTree = getTree(TreeType.NAMESPACE, trailingDot); + assertTreeIsInvalid(trailingDotTree); + + String multiDot = "com.foo..bar"; + TokenTree multiDotTree = getTree(TreeType.NAMESPACE, multiDot); + assertTreeIsInvalid(multiDotTree); + } + + @Test + public void invalidIdentifier() { + String leadingNumber = "1foo"; + TokenTree leadingNumberTree = getTree(TreeType.IDENTIFIER, leadingNumber); + assertTreeIsInvalid(leadingNumberTree); + + String leadingSymbol = "@foo"; + TokenTree leadingSymbolTree = getTree(TreeType.IDENTIFIER, leadingSymbol); + assertTreeIsInvalid(leadingSymbolTree); + } + + @Test + public void invalidBr() { + // Need the "foo" at the end because EOF is a valid BR. + String missingNewline = "\tfoo"; + TokenTree missingNewlineTree = getTree(TreeType.BR, missingNewline); + assertTreeIsInvalid(missingNewlineTree); + } + + @Test + public void invalidComment() { + String invalidSlashes = "/ / Foo"; + TokenTree invalidSlashesTree = getTree(TreeType.COMMENT, invalidSlashes); + assertTreeIsInvalid(invalidSlashesTree); } private static void rootAndChildTypesEqual(TokenTree actualTree, TreeType expectedRoot, TreeType... expectedChildren) { @@ -1321,6 +1852,13 @@ private static void assertTreeIsValid(TokenTree tree) { } } + private static void assertTreeIsInvalid(TokenTree tree) { + TreeCursor cursor = tree.zipper(); + if (cursor.findChildrenByType(TreeType.ERROR).isEmpty()) { + Assertions.fail(() -> "Expected tree to be invalid, but found no errors.\nFull tree:\n" + tree); + } + } + private static TokenTree getTree(TreeType type, String forText) { CapturingTokenizer tokenizer = new CapturingTokenizer(IdlTokenizer.create(forText)); type.parse(tokenizer);