diff --git a/tsql-scriptdom-core/src/test/java/io/github/fupacat/ParseResultTest.java b/tsql-scriptdom-core/src/test/java/io/github/fupacat/ParseResultTest.java new file mode 100644 index 0000000..d0b275f --- /dev/null +++ b/tsql-scriptdom-core/src/test/java/io/github/fupacat/ParseResultTest.java @@ -0,0 +1,43 @@ +package io.github.fupacat; + +import io.github.fupacat.ast.TSqlScript; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class ParseResultTest { + + // TRACEABILITY_MAPPING: REQ-021 + @Test + public void testParseResultWithNullErrorsUsesImmutableEmptyList() { + ParseResult result = new ParseResult<>(null, null); + + assertThat(result.fragment()).isNull(); + assertThat(result.errors()).isEmpty(); + assertThat(result.hasErrors()).isFalse(); + assertThat(result.isSuccess()).isFalse(); + assertThat(result.toString()).contains("success=false"); + assertThatThrownBy(() -> result.errors().add(new ParseError(1, 0, 1, 1, "x"))) + .isInstanceOf(UnsupportedOperationException.class); + } + + // TRACEABILITY_MAPPING: REQ-021 + @Test + public void testParseResultWithErrorsIsNotSuccess() { + TSqlScript fragment = new TSqlScript(); + List errors = new ArrayList<>(); + errors.add(new ParseError(102, 0, 1, 1, null)); + ParseResult result = new ParseResult<>(fragment, errors); + + assertThat(result.fragment()).isSameAs(fragment); + assertThat(result.errors()).hasSize(1); + assertThat(result.errors().get(0).message).isEmpty(); + assertThat(result.hasErrors()).isTrue(); + assertThat(result.isSuccess()).isFalse(); + assertThat(result.toString()).contains("errors=1"); + } +} diff --git a/tsql-scriptdom-core/src/test/java/io/github/fupacat/TSql180ParserTest.java b/tsql-scriptdom-core/src/test/java/io/github/fupacat/TSql180ParserTest.java index 480eabd..f8adeed 100644 --- a/tsql-scriptdom-core/src/test/java/io/github/fupacat/TSql180ParserTest.java +++ b/tsql-scriptdom-core/src/test/java/io/github/fupacat/TSql180ParserTest.java @@ -42,4 +42,44 @@ public void testParseVectorIndexWithVersionCompatibility() throws Exception { assertThat(errors160).isNotEmpty(); assertThat(errors160.get(0).message).contains("CREATE VECTOR INDEX"); } + + // TRACEABILITY_MAPPING: REQ-021, REQ-023 + @Test + public void testParseReturnsNullForNullReader() throws Exception { + TSql180Parser parser = new TSql180Parser(); + List errors = new ArrayList<>(); + + TSqlFragment fragment = parser.parse(null, errors); + + assertThat(fragment).isNull(); + assertThat(errors).isEmpty(); + } + + // TRACEABILITY_MAPPING: REQ-021, REQ-023 + @Test + public void testParseSyntaxErrorIsCollected() throws Exception { + TSql180Parser parser = new TSql180Parser(); + List errors = new ArrayList<>(); + + TSqlFragment fragment = parser.parse(new StringReader("SELECT FROM WHERE;"), errors); + + assertThat(fragment).isNotNull(); + assertThat(errors).isNotEmpty(); + } + + // TRACEABILITY_MAPPING: REQ-021, REQ-023 + @Test + public void testParseWithQuotedIdentifierToggleConstructors() throws Exception { + String sql = "SELECT 1;"; + + List onErrors = new ArrayList<>(); + TSqlFragment onFragment = new TSql180Parser(true).parse(new StringReader(sql), onErrors); + assertThat(onFragment).isNotNull(); + assertThat(onErrors).isEmpty(); + + List offErrors = new ArrayList<>(); + TSqlFragment offFragment = new TSql180Parser(false).parse(new StringReader(sql), offErrors); + assertThat(offFragment).isNotNull(); + assertThat(offErrors).isEmpty(); + } } diff --git a/tsql-scriptdom-core/src/test/java/io/github/fupacat/TSqlParserTokenTest.java b/tsql-scriptdom-core/src/test/java/io/github/fupacat/TSqlParserTokenTest.java index 2dfeb68..869617a 100644 --- a/tsql-scriptdom-core/src/test/java/io/github/fupacat/TSqlParserTokenTest.java +++ b/tsql-scriptdom-core/src/test/java/io/github/fupacat/TSqlParserTokenTest.java @@ -20,4 +20,15 @@ public void testParserTokenAndTokenType() { assertThat(TSqlTokenType.WhiteSpace._isWhitespaceOrComment()).isTrue(); assertThat(TSqlTokenType.Select._isWhitespaceOrComment()).isFalse(); } + + // TRACEABILITY_MAPPING: REQ-006 + @Test + public void testParserTokenDefaultsWhenTypeIsNull() { + TSqlParserToken token = new TSqlParserToken(null, 7, "?", 3, 4); + + assertThat(token._getTokenType()).isEqualTo(TSqlTokenType.None); + assertThat(token.getType()).isZero(); + assertThat(token._getOffset()).isEqualTo(7); + assertThat(token.getText()).isEqualTo("?"); + } } diff --git a/tsql-scriptdom-core/src/test/java/io/github/fupacat/TSqlWhitespaceTokenFilterTest.java b/tsql-scriptdom-core/src/test/java/io/github/fupacat/TSqlWhitespaceTokenFilterTest.java index f1158e5..cda3ab7 100644 --- a/tsql-scriptdom-core/src/test/java/io/github/fupacat/TSqlWhitespaceTokenFilterTest.java +++ b/tsql-scriptdom-core/src/test/java/io/github/fupacat/TSqlWhitespaceTokenFilterTest.java @@ -33,4 +33,73 @@ public void testWhitespaceTokenFilter() throws Exception { Token eof = filter.nextToken(); assertThat(eof.getType()).isEqualTo(TSqlTokenType.EndOfFile.ordinal()); } + + // TRACEABILITY_MAPPING: REQ-007 + @Test + public void testWhitespaceTokenFilterSkipsAdjacentCommentsAndComputesEofBoundary() throws Exception { + List rawTokens = List.of( + new TSqlParserToken(TSqlTokenType.WhiteSpace, 0, " ", 1, 1), + new TSqlParserToken(TSqlTokenType.MultilineComment, 1, "/*a*/", 1, 2), + new TSqlParserToken(TSqlTokenType.MultilineComment, 6, "/*b*/", 1, 7), + new TSqlParserToken(TSqlTokenType.Select, 11, "SELECT", 1, 12), + new TSqlParserToken(TSqlTokenType.WhiteSpace, 17, "\n", 1, 18), + new TSqlParserToken(TSqlTokenType.SingleLineComment, 18, "--tail", 2, 1) + ); + + TSqlWhitespaceTokenFilter filter = new TSqlWhitespaceTokenFilter(true, rawTokens); + + Token select = filter.nextToken(); + assertThat(select.getText()).isEqualTo("SELECT"); + + TSqlParserToken eof = (TSqlParserToken) filter.nextToken(); + assertThat(eof._getTokenType()).isEqualTo(TSqlTokenType.EndOfFile); + assertThat(eof._getOffset()).isEqualTo(24); + assertThat(filter._getLastToken()).isSameAs(eof); + } + + // TRACEABILITY_MAPPING: REQ-007 + @Test + public void testInnerStreamFilterSkipsWhitespaceAndCommentsUntilEof() throws Exception { + antlr.TokenStream inner = new antlr.TokenStream() { + private final List tokens = List.of( + new antlr.CommonToken(TSqlTokenTypes.WhiteSpace, " "), + new antlr.CommonToken(TSqlTokenTypes.MultilineComment, "/*c*/"), + new antlr.CommonToken(TSqlTokenTypes.SingleLineComment, "--d"), + new antlr.CommonToken(TSqlTokenTypes.Select, "SELECT") + ); + private int index = 0; + + @Override + public Token nextToken() { + if (index >= tokens.size()) { + return null; + } + return tokens.get(index++); + } + }; + + TSqlWhitespaceTokenFilter filter = new TSqlWhitespaceTokenFilter(true, inner); + Token token = filter.nextToken(); + assertThat(token.getType()).isEqualTo(TSqlTokenTypes.Select); + assertThat(token.getText()).isEqualTo("SELECT"); + assertThat(filter._getLastToken()._getTokenType()).isEqualTo(TSqlTokenType.None); + + Token eof = filter.nextToken(); + assertThat(eof.getType()).isEqualTo(TSqlTokenTypes.EOF); + assertThat(filter._getLastToken()._getTokenType()).isEqualTo(TSqlTokenType.EndOfFile); + } + + // TRACEABILITY_MAPPING: REQ-007 + @Test + public void testAsciiStringOrQuotedIdentifierConversionFlagUsesQuotedIdentifierMode() throws Exception { + TSqlParserToken ambiguous = new TSqlParserToken(TSqlTokenType.AsciiStringOrQuotedIdentifier, 0, "\"abc\"", 1, 1); + TSqlWhitespaceTokenFilter quotedOn = new TSqlWhitespaceTokenFilter(true, List.of(ambiguous)); + TSqlParserToken onToken = (TSqlParserToken) quotedOn.nextToken(); + assertThat(onToken.convertStringToIdentifier).isTrue(); + + TSqlParserToken ambiguousOff = new TSqlParserToken(TSqlTokenType.AsciiStringOrQuotedIdentifier, 0, "\"abc\"", 1, 1); + TSqlWhitespaceTokenFilter quotedOff = new TSqlWhitespaceTokenFilter(false, List.of(ambiguousOff)); + TSqlParserToken offToken = (TSqlParserToken) quotedOff.nextToken(); + assertThat(offToken.convertStringToIdentifier).isFalse(); + } }