2021-05-04 09:58:49 +02:00
from bs4 import BeautifulSoup
2022-02-01 13:25:23 +01:00
from pathlib import Path
2021-05-04 09:58:49 +02:00
from typing import Final
from rspec_tools . errors import RuleValidationError
from rspec_tools . rules import LanguageSpecificRule
2022-02-08 17:34:53 +01:00
from rspec_tools . utils import LANG_TO_SOURCE
2021-05-04 09:58:49 +02:00
2022-07-07 14:06:43 +02:00
import re
2021-05-04 09:58:49 +02:00
# The list of all the sections currently accepted by the script.
# The list includes multiple variants for each title because they all occur
# in the migrated RSPECs.
# Further work required to shorten the list by renaming the sections in some RSPECS
# to keep only on version for each title.
2022-02-01 13:25:23 +01:00
SECTION_NAMES_PATH = Path ( __file__ ) . parent . parent . parent . parent . joinpath ( ' docs/section_names.adoc ' )
SECTION_NAMES_FILE = SECTION_NAMES_PATH . read_text ( encoding = ' utf-8 ' ) . split ( ' \n ' )
ACCEPTED_SECTION_NAMES : Final [ list [ str ] ] = [ s . replace ( ' * ' , ' ' ) . strip ( ) for s in SECTION_NAMES_FILE if s . strip ( ) ]
2022-07-07 14:06:43 +02:00
# The list of all the framework names currently accepted by the script.
FRAMEWORK_NAMES_PATH = Path ( __file__ ) . parent . parent . parent . parent . joinpath ( ' docs/allowed_framework_names.adoc ' )
FRAMEWORK_NAMES_FILE = FRAMEWORK_NAMES_PATH . read_text ( encoding = ' utf-8 ' ) . split ( ' \n ' )
ACCEPTED_FRAMEWORK_NAMES : Final [ list [ str ] ] = [ s . replace ( ' * ' , ' ' ) . strip ( ) for s in FRAMEWORK_NAMES_FILE if s . strip ( ) ]
2021-05-04 09:58:49 +02:00
def validate_section_names ( rule_language : LanguageSpecificRule ) :
descr = rule_language . description
2022-02-01 13:25:23 +01:00
for h2 in descr . find_all ( ' h2 ' ) :
2021-05-04 09:58:49 +02:00
name = h2 . text . strip ( )
if name not in ACCEPTED_SECTION_NAMES :
raise RuleValidationError ( f ' Rule { rule_language . id } has unconventional header " { name } " ' )
2022-07-07 14:06:43 +02:00
def validate_how_to_fix_it_subsections ( rule_language : LanguageSpecificRule ) :
descr = rule_language . description
frameworks_counter = 0
for h3 in descr . find_all ( ' h3 ' ) :
name = h3 . text . strip ( )
# It is important that the Regex here matches the one used by the analyzers when loading the rules content
result = re . search ( ' How to fix it in (?:(?:an|a|the) \\ s)?(.*) ' , name )
if result is not None :
if result . group ( 1 ) not in ACCEPTED_FRAMEWORK_NAMES :
raise RuleValidationError ( f ' Rule { rule_language . id } has a " How to fix it " section for an unsupported framework: " { result . group ( 1 ) } " ' )
else :
frameworks_counter + = 1
how_to_fix_it_section = descr . find ( ' h2 ' , string = ' How to fix it? ' )
if how_to_fix_it_section is not None :
if frameworks_counter == 0 :
raise RuleValidationError ( f ' Rule { rule_language . id } has a " How to fix it " section but is missing subsections related to frameworks ' )
if frameworks_counter > 6 :
raise RuleValidationError ( f ' Rule { rule_language . id } has more than 6 " How to fix it " subsections. Please ensure this limit can be increased with PM/UX teams ' )
elif frameworks_counter > 0 :
raise RuleValidationError ( f ' Rule { rule_language . id } has " How to fix it " subsections for frameworks outside a defined " How to fix it? " section ' )
2021-09-30 11:52:56 +02:00
def validate_section_levels ( rule_language : LanguageSpecificRule ) :
h1 = rule_language . description . find ( ' h1 ' )
if h1 is not None :
name = h1 . text . strip ( )
raise RuleValidationError ( f ' Rule { rule_language . id } has level-0 header " { name } " ' )
2022-02-01 13:25:23 +01:00
def validate_one_parameter ( child , id ) :
if child . name != ' div ' or child [ ' class ' ] [ 0 ] != ' sidebarblock ' :
raise RuleValidationError ( f ' Rule { id } should use `****` blocks for each parameter ' )
for div_child in child . children :
if div_child . name is not None :
if div_child [ ' class ' ] [ 0 ] != ' content ' :
raise RuleValidationError ( f ' Rule { id } should use `****` blocks for each parameter ' )
if div_child . p is None :
raise RuleValidationError ( f ' Rule { id } should have a description for each parameter ' )
if div_child . find ( ' div ' , ' title ' ) is None :
raise RuleValidationError ( f ' Rule { id } should have a parameter name declared with `.name` before the bock, for each parameter ' )
def validate_parameters ( rule_language : LanguageSpecificRule ) :
for h3 in rule_language . description . find_all ( ' h3 ' ) :
name = h3 . text . strip ( )
if name == ' Parameters ' :
for child in h3 . parent . children :
if child . name is None or child == h3 or child . name == ' hr ' :
continue
validate_one_parameter ( child , rule_language . id )
2022-02-04 17:28:24 +01:00
def highlight_name ( rule_language : LanguageSpecificRule ) :
2022-02-08 17:34:53 +01:00
if ( rule_language . language in LANG_TO_SOURCE ) :
return LANG_TO_SOURCE [ rule_language . language ]
2022-02-04 17:28:24 +01:00
return rule_language . language
def known_highlight ( language ) :
2022-02-08 17:34:53 +01:00
return language in LANG_TO_SOURCE . values ( )
2022-02-04 17:28:24 +01:00
def validate_source_language ( rule_language : LanguageSpecificRule ) :
descr = rule_language . description
for h2 in descr . findAll ( ' h2 ' ) :
name = h2 . text . strip ( )
if name . startswith ( ' Compliant ' ) or name . startswith ( ' Noncompliant ' ) :
for pre in h2 . parent . find_all ( ' pre ' ) :
if not pre . has_attr ( ' class ' ) or pre [ ' class ' ] [ 0 ] != u ' highlight ' or not pre . code or not pre . code . has_attr ( ' data-lang ' ) :
raise RuleValidationError ( f ''' Rule { rule_language . id } has non highlighted code example in section " { name } " .
Use [ source , { highlight_name ( rule_language ) } ] or [ source , text ] before the opening ' ---- ' . ''' )
elif not known_highlight ( pre . code [ ' data-lang ' ] ) :
raise RuleValidationError ( f ''' Rule { rule_language . id } has unknown language " { pre . code [ ' data-lang ' ] } " in code example in section " { name } " .
Are you looking for " { highlight_name(rule_language)} " ? ''' )