| #!/usr/bin/env python3 |
| # Copyright (C) 2022 The Android Open Source Project |
| # |
| # 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. |
| |
| import re |
| from typing import Union, List |
| |
| from python.generators.stdlib_docs import stdlib |
| from python.generators.stdlib_docs.utils import Pattern, Errors |
| |
| |
| # Whether the only typed comment in provided comment segment is of type |
| # `comment_type`. |
| def validate_typed_comment( |
| comment_segment: str, |
| comment_type: str, |
| docs: 'stdlib.AnyDocs', |
| ) -> Errors: |
| for line in comment_segment: |
| # Ignore only '--' line. |
| if line == "--": |
| continue |
| |
| m = re.match(Pattern['typed_line'], line) |
| |
| # Ignore untyped lines |
| if not m: |
| continue |
| |
| line_type = m.group(1) |
| |
| if line_type != comment_type: |
| return [( |
| f"Wrong comment type. Expected '{comment_type}', got '{line_type}'.\n" |
| f"'{docs.name}' in {docs.path}:\n'{line}'\n")] |
| return [] |
| |
| |
| # Whether comment segment about columns contain proper schema. Can be matched |
| # against parsed SQL data by setting `use_data_from_sql`. |
| def validate_columns( |
| docs: Union['stdlib.TableViewDocs', 'stdlib.ViewFunctionDocs'], |
| use_data_from_sql=False) -> Errors: |
| errors = validate_typed_comment(docs.columns, "column", docs) |
| |
| if errors: |
| return errors |
| |
| if use_data_from_sql: |
| cols_from_sql = docs.data_from_sql["columns"] |
| |
| for line in docs.columns: |
| # Ignore only '--' line. |
| if line == "--" or not line.startswith("-- @column"): |
| continue |
| |
| # Look for '-- @column' line as a column description |
| m = re.match(Pattern['column'], line) |
| if not m: |
| errors.append(f"Wrong column description.\n" |
| f"'{docs.name}' in {docs.path}:\n'{line}'\n") |
| continue |
| |
| if not m.group(2).strip(): |
| errors.append(f"No description for column '{m.group(1)}'.\n" |
| f"'{docs.name}' in {docs.path}:\n'{line}'\n") |
| continue |
| |
| if not use_data_from_sql: |
| return errors |
| |
| col_name = m.group(1) |
| if col_name not in cols_from_sql: |
| errors.append(f"There is no argument '{col_name}' specified in code.\n" |
| f"'{docs.name}' in {docs.path}:\n'{line}'\n") |
| continue |
| |
| cols_from_sql.pop(col_name) |
| |
| if not use_data_from_sql: |
| errors.append(f"Missing columns for {docs.name}\n{docs.path}\n") |
| return errors |
| |
| if not cols_from_sql: |
| return errors |
| |
| errors.append( |
| f"Missing documentation of columns: {list(cols_from_sql.keys())}.\n" |
| f"'{docs.name}' in {docs.path}:\n") |
| return errors |
| |
| |
| # Whether comment segment about columns contain proper schema. Matches against |
| # parsed SQL data. |
| def validate_args(docs: Union['stdlib.FunctionDocs', 'stdlib.ViewFunctionDocs'] |
| ) -> Errors: |
| if not docs.args: |
| return [] |
| |
| errors = validate_typed_comment(docs.args, "arg", docs) |
| |
| if errors: |
| return errors |
| |
| args_from_sql = docs.data_from_sql["args"] |
| for line in docs.args: |
| # Ignore only '--' line. |
| if line == "--" or not line.startswith("-- @"): |
| continue |
| |
| m = re.match(Pattern['args'], line) |
| if m is None: |
| errors.append("The arg docs formatting is wrong. It should be:\n" |
| "-- @arg [a-z_]* [A-Z]* {desc}\n" |
| f"'{docs.name}' in {docs.path}:\n'{line}'\n") |
| return errors |
| |
| arg_name, arg_type = m.group(1), m.group(2) |
| if arg_name not in args_from_sql: |
| errors.append(f"There is not argument '{arg_name}' specified in code.\n" |
| f"'{docs.name}' in {docs.path}:\n'{line}'\n") |
| continue |
| |
| arg_type_from_sql = args_from_sql.pop(arg_name) |
| if arg_type != arg_type_from_sql: |
| errors.append(f"In the code, the type of '{arg_name}' is " |
| f"'{arg_type_from_sql}', but according to the docs " |
| f"it is '{arg_type}'.\n" |
| f"'{docs.name}' in {docs.path}:\n'{line}'\n") |
| |
| if not args_from_sql: |
| return errors |
| |
| errors.append( |
| f"Missing documentation of args: {list(args_from_sql.keys())}.\n" |
| f"'{docs.name}' in {docs.path}\n") |
| return errors |
| |
| |
| # Whether comment segment about return contain proper schema. Matches against |
| # parsed SQL data. |
| def validate_ret(docs: "stdlib.FunctionDocs") -> Errors: |
| errors = validate_typed_comment(docs.ret, "ret", docs) |
| if errors: |
| return errors |
| |
| ret_type_from_sql = docs.data_from_sql["ret"] |
| |
| for line in docs.ret: |
| # Ignore only '--' line. |
| if line == "--" or not line.startswith("-- @ret"): |
| continue |
| |
| m = re.match(Pattern['return_arg'], line) |
| if m is None: |
| return [("The return docs formatting is wrong. It should be:\n" |
| "-- @ret [A-Z]* {desc}\n" |
| f"'{docs.name}' in {docs.path}:\n'{line}'\n")] |
| docs_ret_type = m.group(1) |
| if ret_type_from_sql != docs_ret_type: |
| return [(f"The return type in docs is '{docs_ret_type}', " |
| f"but it is {ret_type_from_sql} in code.\n" |
| f"'{docs.name}' in {docs.path}:\n'{line}'\n")] |
| return [] |