| // Copyright 2015 The Bazel Authors. All rights reserved. |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| package net.starlark.java.syntax; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.function.Supplier; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Tests for {@code NodeVisitor} */ |
| @RunWith(JUnit4.class) |
| public final class NodeVisitorTest { |
| |
| Supplier<IdentGatherer> gathererFactory = IdentGatherer::new; |
| |
| FileOptions fileOptions = FileOptions.builder().allowTypeSyntax(true).build(); |
| |
| private StarlarkFile parse(String... lines) throws SyntaxError.Exception { |
| ParserInput input = ParserInput.fromLines(lines); |
| StarlarkFile file = StarlarkFile.parse(input, fileOptions); |
| if (!file.ok()) { |
| throw new SyntaxError.Exception(file.errors()); |
| } |
| return file; |
| } |
| |
| /** Records all identifiers in the order they were seen, including duplicates. */ |
| private static class IdentGatherer extends NodeVisitor { |
| final List<String> idents = new ArrayList<>(); |
| |
| static IdentGatherer skippingNonSymbolIdentifiers() { |
| IdentGatherer gatherer = new IdentGatherer(); |
| gatherer.skipNonSymbolIdentifiers = true; |
| return gatherer; |
| } |
| |
| @Override |
| public void visit(Identifier node) { |
| idents.add(node.getName()); |
| } |
| } |
| |
| /** |
| * Asserts that the traversed identifiers (in order, including duplicates) of the given source |
| * code match the expected identifiers, which is supplied as a space-delimited string. |
| */ |
| public void assertIdentsAre(String src, String expectedIdents) throws Exception { |
| StarlarkFile file = parse(src); |
| IdentGatherer visitor = gathererFactory.get(); |
| visitor.visit(file); |
| assertThat(visitor.idents).containsExactlyElementsIn(expectedIdents.split(" ")).inOrder(); |
| } |
| |
| @Test |
| public void blockAndSimpleStatements() throws Exception { |
| assertIdentsAre( |
| """ |
| load("...", "a", b="c") |
| d = e |
| f |
| pass |
| g, h[i] = j + 1 + "xyz" + 0.0 |
| """, |
| // "c" is omitted because we don't currently visit the original name in a load binding. |
| "a b d e f g h i j"); |
| } |
| |
| @Test |
| public void controlStatements() throws Exception { |
| assertIdentsAre( |
| """ |
| for a in b: |
| if c: |
| break |
| else: |
| continue |
| """, |
| "a b c"); |
| } |
| |
| @Test |
| public void simpleExpressions() throws Exception { |
| assertIdentsAre( |
| """ |
| a + b if c else d.e |
| {f: g, h: [i, j]} |
| not k[l:m] |
| """, |
| "a b c d e f g h i j k l m"); |
| } |
| |
| @Test |
| public void comprehensions() throws Exception { |
| assertIdentsAre( |
| """ |
| [a for b, c in d if e for f in {g: h for i in j}] |
| """, |
| "a b c d e f g h i j"); |
| } |
| |
| @Test |
| public void calls() throws Exception { |
| assertIdentsAre( |
| """ |
| a(b, c=d, *e, **f) |
| """, |
| "a b c d e f"); |
| } |
| |
| @Test |
| public void functionDefs() throws Exception { |
| assertIdentsAre( |
| """ |
| def a(b, c=d, *e, **f): |
| g |
| """, |
| "a b c d e f g"); |
| assertIdentsAre( |
| """ |
| def a(*, b, c=d): |
| return |
| """, |
| "a b c d"); |
| } |
| |
| @Test |
| public void typeAnnotations() throws Exception { |
| assertIdentsAre( |
| """ |
| def a[b, c](d : e[f], g: h) -> i: |
| pass |
| """, |
| "a b c d e f g h i"); |
| |
| assertIdentsAre( |
| """ |
| a : b |
| c : d = e |
| """, |
| "a b c d e"); |
| } |
| |
| @Test |
| public void typeDeclarations() throws Exception { |
| assertIdentsAre( |
| """ |
| type a[b, c] = d |
| """, |
| "a b c d"); |
| } |
| |
| @Test |
| public void ellipsis() throws Exception { |
| // Really, this is just a test that we defined NodeVisitor#visit(Ellipsis) to exist... |
| fileOptions = |
| FileOptions.builder().allowTypeSyntax(true).tolerateInvalidTypeExpressions(true).build(); |
| assertIdentsAre( |
| """ |
| def a() -> ...: |
| pass |
| """, |
| "a"); |
| } |
| |
| @Test |
| public void typeOperations() throws Exception { |
| assertIdentsAre( |
| """ |
| cast(a, b) |
| isinstance(c, d) |
| """, |
| "a b c d"); |
| } |
| |
| @Test |
| public void skipNonSymbolIdentifiers() throws Exception { |
| gathererFactory = IdentGatherer::skippingNonSymbolIdentifiers; |
| |
| assertIdentsAre( |
| """ |
| load("...", a="b") |
| c(d=e.f) |
| """, |
| // No b, no d, no f. |
| "a c e"); |
| assertIdentsAre( |
| """ |
| def a(b, c=d): |
| pass |
| """, |
| // Keyword param identifiers ("c") are still visited. |
| "a b c d"); |
| } |
| } |