#!/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 []