| // Copyright 2017 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 com.google.devtools.skylark.skylint; |
| |
| import com.google.common.truth.Truth; |
| import com.google.devtools.skylark.skylint.DocstringUtils.DocstringInfo; |
| import com.google.devtools.skylark.skylint.DocstringUtils.DocstringParseError; |
| import com.google.devtools.skylark.skylint.DocstringUtils.ParameterDoc; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.JUnit4; |
| |
| /** Tests the {@link DocstringUtils} class. */ |
| @RunWith(JUnit4.class) |
| public class DocstringUtilsTest { |
| @Test |
| public void oneLineDocstring() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringInfo info = DocstringUtils.parseDocstring("summary", 0, errors); |
| Truth.assertThat(info.summary).isEqualTo("summary"); |
| Truth.assertThat(info.parameters).isEmpty(); |
| Truth.assertThat(info.longDescription).isEmpty(); |
| Truth.assertThat(errors).isEmpty(); |
| } |
| |
| @Test |
| public void missingBlankLineAfterSummary() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringInfo info = DocstringUtils.parseDocstring("summary\nfoo", 0, errors); |
| Truth.assertThat(info.summary).isEqualTo("summary"); |
| Truth.assertThat(info.parameters).isEmpty(); |
| Truth.assertThat(info.longDescription).isEqualTo("foo"); |
| Truth.assertThat(errors.toString()) |
| .contains("2: the one-line summary should be followed by a blank line"); |
| } |
| |
| @Test |
| public void multiParagraphDocstring() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringInfo info = DocstringUtils.parseDocstring("summary\n\nfoo\n\nbar\n", 0, errors); |
| Truth.assertThat(info.summary).isEqualTo("summary"); |
| Truth.assertThat(info.parameters).isEmpty(); |
| Truth.assertThat(info.longDescription).isEqualTo("foo\n\nbar"); |
| Truth.assertThat(errors).isEmpty(); |
| } |
| |
| @Test |
| public void inconsistentIndentation() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringInfo info = |
| DocstringUtils.parseDocstring("summary\n" + "\n" + " foo\n" + "bar\n ", 2, errors); |
| Truth.assertThat(info.summary).isEqualTo("summary"); |
| Truth.assertThat(info.parameters).isEmpty(); |
| Truth.assertThat(info.longDescription).isEqualTo("foo\nbar"); |
| Truth.assertThat(errors.toString()) |
| .contains("4: line indented too little (here: 0 spaces; expected: 2 spaces)"); |
| |
| errors = new ArrayList<>(); |
| info = |
| DocstringUtils.parseDocstring( |
| "summary\n" |
| + "\n" |
| + " baseline indentation\n" |
| + " more indentation can be useful (e.g. for example code)\n" |
| + " ", |
| 2, |
| errors); |
| Truth.assertThat(info.summary).isEqualTo("summary"); |
| Truth.assertThat(info.parameters).isEmpty(); |
| Truth.assertThat(info.longDescription) |
| .isEqualTo( |
| "baseline indentation\n more indentation can be useful (e.g. for example code)"); |
| Truth.assertThat(errors).isEmpty(); |
| } |
| |
| @Test |
| public void inconsistentIndentationInSection() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringInfo info = |
| DocstringUtils.parseDocstring( |
| "summary\n" |
| + "\n" |
| + "Returns:\n" |
| + " only one space indentation\n" |
| + "unindented line after", |
| 0, |
| errors); |
| Truth.assertThat(info.returns).isEqualTo("only one space indentation"); |
| Truth.assertThat(info.longDescription).isEqualTo("unindented line after"); |
| Truth.assertThat(errors.toString()) |
| .contains( |
| "4: text in a section has to be indented by two spaces" |
| + " (relative to the left margin of the docstring)"); |
| Truth.assertThat(errors.toString()).contains("5: end of section without blank line"); |
| } |
| |
| @Test |
| public void inconsistentIndentationInParameters() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringInfo info = |
| DocstringUtils.parseDocstring( |
| "summary\n" |
| + "\n" |
| + "Args:\n" |
| + " param0: two spaces indentation\n" |
| + " param1: only one space indentation\n" |
| + " only two spaces indentation in continued line\n" |
| + "unindented line after", |
| 0, |
| errors); |
| Truth.assertThat(info.summary).isEqualTo("summary"); |
| Truth.assertThat(info.parameters).hasSize(2); |
| ParameterDoc param0 = info.parameters.get(0); |
| Truth.assertThat(param0.description).isEqualTo("two spaces indentation"); |
| ParameterDoc param1 = info.parameters.get(1); |
| Truth.assertThat(param1.description) |
| .isEqualTo("only one space indentation\n only two spaces indentation in continued line"); |
| Truth.assertThat(errors.toString()) |
| .contains("5: inconsistent indentation of parameter lines (before: 2; here: 1 spaces)"); |
| Truth.assertThat(errors.toString()).contains("7: end of 'Args' section without blank line"); |
| } |
| |
| @Test |
| public void whitespaceOnlyLinesCountAsBlank() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringInfo info = |
| DocstringUtils.parseDocstring( |
| "summary\n" |
| + " \n" // if not blank, would have too much indent |
| + " description\n" |
| + " \n" // if not blank, would have too little indent |
| + " Args:\n" |
| + " foo: foo description\n" |
| + " \n" // if not blank, would be indented just the right amount to end param |
| // description but not Args section |
| + " Returns:\n" |
| + " returns description\n" |
| + " \n" // if not blank, would be section content that's indented too little |
| + " ", |
| 4, |
| errors); |
| Truth.assertThat(info.parameters).hasSize(1); |
| Truth.assertThat(info.parameters.get(0).description).isEqualTo("foo description"); |
| Truth.assertThat(info.returns).isEqualTo("returns description"); |
| Truth.assertThat(errors).isEmpty(); |
| } |
| |
| @Test |
| public void closingQuoteMustBeProperlyIndented() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringUtils.parseDocstring("summary", 4, errors); |
| Truth.assertThat(errors).isEmpty(); |
| |
| errors = new ArrayList<>(); |
| DocstringUtils.parseDocstring( |
| "summary\n" |
| + "\n" |
| + " more description", |
| 4, errors); |
| Truth.assertThat(errors.toString()) |
| .contains("3: closing docstring quote should be on its own line, indented the same as the " |
| + "opening quote"); |
| |
| errors = new ArrayList<>(); |
| DocstringUtils.parseDocstring( |
| "summary\n" |
| + "\n" |
| + " more description\n" |
| + " ", // too little |
| 4, errors); |
| Truth.assertThat(errors.toString()) |
| .contains("4: closing docstring quote should be indented the same as the opening quote"); |
| |
| errors = new ArrayList<>(); |
| DocstringUtils.parseDocstring( |
| "summary\n" |
| + "\n" |
| + " more description\n" |
| + " ", // too much |
| 4, errors); |
| Truth.assertThat(errors.toString()) |
| .contains("4: closing docstring quote should be indented the same as the opening quote"); |
| |
| errors = new ArrayList<>(); |
| DocstringUtils.parseDocstring( |
| "summary\n" |
| + "\n" |
| + " more description\n" |
| + "", // too little (empty line special case) |
| 4, errors); |
| Truth.assertThat(errors.toString()) |
| .contains("4: closing docstring quote should be indented the same as the opening quote"); |
| } |
| |
| @Test |
| public void emptySection() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringUtils.parseDocstring( |
| "summary\n" + "\n" + "Args:\n" + "More description.\n", 0, errors); |
| Truth.assertThat(errors.toString()).contains("3: section is empty or badly formatted"); |
| |
| errors = new ArrayList<>(); |
| DocstringUtils.parseDocstring( |
| "summary\n" + "\n" + "Returns:\n" + "More description\n", 0, errors); |
| Truth.assertThat(errors.toString()).contains("3: section is empty"); |
| |
| errors = new ArrayList<>(); |
| DocstringUtils.parseDocstring( |
| "summary\n" + "\n" + "Deprecated:\n" + "More description\n", 0, errors); |
| Truth.assertThat(errors.toString()).contains("3: section is empty"); |
| } |
| |
| @Test |
| public void emptyParamDescription() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringUtils.parseDocstring("summary\n" + "\n" + "Args:\n" + "" + " foo: \n\n", 0, errors); |
| Truth.assertThat(errors.toString()).contains("4: empty parameter description for 'foo'"); |
| } |
| |
| @Test |
| public void sectionOnOneLine() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringUtils.parseDocstring("summary\n" + "\n" + "Returns: foo\n", 0, errors); |
| Truth.assertThat(errors).hasSize(1); |
| Truth.assertThat(errors.get(0).toString()) |
| .startsWith("3: the return value should be documented in a section"); |
| } |
| |
| @Test |
| public void docstringReturn() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringInfo info = |
| DocstringUtils.parseDocstring( |
| "summary\n" |
| + "\n" |
| + " Returns:\n" |
| + " line 1\n" |
| + "\n" |
| + " line 2\n" |
| + " ", |
| 2, |
| errors); |
| Truth.assertThat(info.summary).isEqualTo("summary"); |
| Truth.assertThat(info.returns).isEqualTo("line 1\n\nline 2"); |
| Truth.assertThat(info.longDescription).isEmpty(); |
| Truth.assertThat(errors).isEmpty(); |
| } |
| |
| @Test |
| public void docstringDeprecated() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringInfo info = |
| DocstringUtils.parseDocstring( |
| "summary\n" |
| + "\n" |
| + " Deprecated:\n" |
| + " line 1\n" |
| + "\n" |
| + " line 2\n" |
| + " ", |
| 2, |
| errors); |
| Truth.assertThat(info.summary).isEqualTo("summary"); |
| Truth.assertThat(info.deprecated).isEqualTo("line 1\n\nline 2"); |
| Truth.assertThat(info.longDescription).isEmpty(); |
| Truth.assertThat(errors).isEmpty(); |
| } |
| |
| @Test |
| public void docstringDeprecatedTheWrongWay() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringInfo info = |
| DocstringUtils.parseDocstring("summary\n" + "\n" + " Deprecated: foo\n ", 2, errors); |
| Truth.assertThat(info.summary).isEqualTo("summary"); |
| Truth.assertThat(info.deprecated).isEqualTo("Deprecated: foo"); |
| Truth.assertThat(info.longDescription).isEqualTo("Deprecated: foo"); |
| Truth.assertThat(errors).hasSize(1); |
| Truth.assertThat(errors.get(0).toString()) |
| .startsWith( |
| "3: use a 'Deprecated:' section for deprecations, similar to a 'Returns:' section"); |
| |
| errors = new ArrayList<>(); |
| info = DocstringUtils.parseDocstring( |
| "summary\n" + "\n" + " This is DEPRECATED.\n ", |
| 2, |
| errors); |
| Truth.assertThat(info.summary).isEqualTo("summary"); |
| Truth.assertThat(info.deprecated).isEqualTo("This is DEPRECATED."); |
| Truth.assertThat(info.longDescription).isEqualTo("This is DEPRECATED."); |
| Truth.assertThat(errors).hasSize(1); |
| Truth.assertThat(errors.get(0).toString()) |
| .startsWith( |
| "3: use a 'Deprecated:' section for deprecations, similar to a 'Returns:' section"); |
| } |
| |
| @Test |
| public void docstringParameters() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringInfo info = |
| DocstringUtils.parseDocstring( |
| "summary\n" |
| + "\n" |
| + " Args:\n" |
| + " param1: multi-\n" |
| + " line\n" |
| + " param2 (mutable, unused): bar\n" |
| + " *args: args\n" |
| + " **kwargs: kwargs\n" |
| + " ", |
| 2, |
| errors); |
| Truth.assertThat(info.summary).isEqualTo("summary"); |
| Truth.assertThat(info.parameters).hasSize(4); |
| Truth.assertThat(info.longDescription).isEmpty(); |
| ParameterDoc firstParam = info.parameters.get(0); |
| ParameterDoc secondParam = info.parameters.get(1); |
| ParameterDoc thirdParam = info.parameters.get(2); |
| ParameterDoc fourthParam = info.parameters.get(3); |
| |
| Truth.assertThat(firstParam.parameterName).isEqualTo("param1"); |
| Truth.assertThat(firstParam.attributes).isEmpty(); |
| Truth.assertThat(firstParam.description).isEqualTo("multi-\n line"); |
| |
| Truth.assertThat(secondParam.parameterName).isEqualTo("param2"); |
| Truth.assertThat(secondParam.attributes).isEqualTo(Arrays.asList("mutable", "unused")); |
| Truth.assertThat(secondParam.description).isEqualTo("bar"); |
| |
| Truth.assertThat(thirdParam.parameterName).isEqualTo("*args"); |
| Truth.assertThat(thirdParam.attributes).isEmpty(); |
| Truth.assertThat(thirdParam.description).isEqualTo("args"); |
| |
| Truth.assertThat(fourthParam.parameterName).isEqualTo("**kwargs"); |
| Truth.assertThat(fourthParam.attributes).isEmpty(); |
| Truth.assertThat(fourthParam.description).isEqualTo("kwargs"); |
| |
| Truth.assertThat(errors).isEmpty(); |
| } |
| |
| @Test |
| public void docstringParametersWithBlankLines() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringInfo info = |
| DocstringUtils.parseDocstring( |
| "summary\n" |
| + "\n" |
| + " Args:\n" |
| + " param1: multi-\n" |
| + "\n" |
| + " line\n" |
| + "\n" |
| + " param2: foo\n" |
| + " ", |
| 2, |
| errors); |
| Truth.assertThat(info.summary).isEqualTo("summary"); |
| Truth.assertThat(info.parameters).hasSize(2); |
| Truth.assertThat(info.longDescription).isEmpty(); |
| ParameterDoc firstParam = info.parameters.get(0); |
| ParameterDoc secondParam = info.parameters.get(1); |
| |
| Truth.assertThat(firstParam.parameterName).isEqualTo("param1"); |
| Truth.assertThat(firstParam.attributes).isEmpty(); |
| Truth.assertThat(firstParam.description).isEqualTo("multi-\n\n line"); |
| |
| Truth.assertThat(secondParam.parameterName).isEqualTo("param2"); |
| Truth.assertThat(secondParam.attributes).isEmpty(); |
| Truth.assertThat(secondParam.description).isEqualTo("foo"); |
| |
| Truth.assertThat(errors).isEmpty(); |
| } |
| |
| @Test |
| public void sectionNotPrecededByNewline() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringUtils.parseDocstring("summary\n" + "\n" + " foo\n" + " Args:", 2, errors); |
| Truth.assertThat(errors.toString()).contains("4: section should be preceded by a blank line"); |
| errors = new ArrayList<>(); |
| DocstringUtils.parseDocstring("summary\n" + "\n" + " foo\n" + " Returns:", 2, errors); |
| Truth.assertThat(errors.toString()).contains("4: section should be preceded by a blank line"); |
| errors = new ArrayList<>(); |
| DocstringUtils.parseDocstring("summary\n" + "\n" + " foo\n" + " Deprecated:", 2, errors); |
| Truth.assertThat(errors.toString()).contains("4: section should be preceded by a blank line"); |
| } |
| |
| @Test |
| public void duplicatedSectionsError() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringInfo info = |
| DocstringUtils.parseDocstring( |
| "summary\n" |
| + "\n" |
| + " Args:\n" |
| + " param1: foo\n" |
| + "\n" |
| + " Args:\n" |
| + " param2: bar\n" |
| + "\n" |
| + " Returns:\n" |
| + " foo\n" |
| + "\n" |
| + " Returns:\n" |
| + " bar\n" |
| + "\n" |
| + " Deprecated:\n" |
| + " foo\n" |
| + "\n" |
| + " Deprecated:\n" |
| + " bar\n" |
| + "\n" |
| + " description", |
| 2, |
| errors); |
| Truth.assertThat(info.parameters).hasSize(2); |
| Truth.assertThat(errors.toString()).contains("6: duplicate 'Args:' section"); |
| Truth.assertThat(errors.toString()).contains("12: duplicate 'Returns:' section"); |
| Truth.assertThat(errors.toString()).contains("18: duplicate 'Deprecated:' section"); |
| } |
| |
| @Test |
| public void sectionsInWrongOrderError() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringInfo info = |
| DocstringUtils.parseDocstring( |
| "summary\n" |
| + "\n" |
| + " Deprecated:\n" |
| + " bar\n" |
| + "\n" |
| + " Returns:\n" |
| + " foo\n" |
| + "\n" |
| + " Args:\n" |
| + " param1: foo\n" |
| + "\n" |
| + " description\n" |
| + " ", |
| 2, |
| errors); |
| Truth.assertThat(info.summary).isEqualTo("summary"); |
| Truth.assertThat(info.parameters).hasSize(1); |
| Truth.assertThat(info.returns).isEqualTo("foo"); |
| Truth.assertThat(info.deprecated).isEqualTo("bar"); |
| Truth.assertThat(info.longDescription).isEqualTo("description"); |
| Truth.assertThat(errors.toString()) |
| .contains("9: 'Args:' section should go before the 'Returns:' section"); |
| Truth.assertThat(errors.toString()) |
| .contains("9: 'Args:' section should go before the 'Deprecated:' section"); |
| Truth.assertThat(errors.toString()) |
| .contains("6: 'Returns:' section should go before the 'Deprecated:' section"); |
| Truth.assertThat(errors.toString()) |
| .contains(("12: description body should go before the special sections")); |
| } |
| |
| @Test |
| public void noRepeatedErrorAboutWrongOrder() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringInfo info = |
| DocstringUtils.parseDocstring( |
| "summary\n" |
| + "\n" |
| + " Args:\n" |
| + " param1: foo\n" |
| + "\n" |
| + " line 1\n" |
| + " line 2\n" |
| + " ", |
| 2, |
| errors); |
| Truth.assertThat(info.summary).isEqualTo("summary"); |
| Truth.assertThat(info.parameters).hasSize(1); |
| Truth.assertThat(info.longDescription).isEqualTo("line 1\nline 2"); |
| Truth.assertThat(errors).hasSize(1); |
| Truth.assertThat(errors.get(0).toString()) |
| .isEqualTo("6: description body should go before the special sections"); |
| } |
| |
| @Test |
| public void invalidParameterDoc() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringInfo info = |
| DocstringUtils.parseDocstring( |
| "summary\n" |
| + "\n" |
| + " Args:\n" |
| + " invalid parameter doc\n" |
| + "\n" |
| + " description", |
| 2, |
| errors); |
| Truth.assertThat(info.parameters).isEmpty(); |
| Truth.assertThat(errors.toString()) |
| .contains( |
| "4: invalid parameter documentation" |
| + " (expected format: \"parameter_name: documentation\")."); |
| } |
| |
| @Test |
| public void parseErrorContainsCorrectLine() throws Exception { |
| List<DocstringParseError> errors = new ArrayList<>(); |
| DocstringUtils.parseDocstring( |
| "summary\n" + "\n" + " Args:\n" + " invalid parameter doc\n", 2, errors); |
| Truth.assertThat(errors.get(0).line).isEqualTo(" invalid parameter doc"); |
| } |
| } |