meta/style.pm (958 lines of code) (raw):

#!/usr/bin/perl # # Copyright (c) 2014 Microsoft Open Technologies, Inc. # # 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 # # THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR # CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT # LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS # FOR A PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT. # # See the Apache Version 2.0 License for specific language governing # permissions and limitations under the License. # # Microsoft would like to thank the following companies for their review and # assistance with these files: Intel Corporation, Mellanox Technologies Ltd, # Dell Products, L.P., Facebook, Inc., Marvell International Ltd. # # @file style.pm # # @brief This module defines SAI Metadata Style Parser # package style; use strict; use warnings; use diagnostics; use Data::Dumper; use utils; require Exporter; sub RunAspell { my $hash = shift; my %wordsToCheck = %{ $hash }; if (not -e "/usr/bin/aspell") { LogError "ASPELL IS NOT PRESENT, please install aspell"; return; } LogInfo "Running Aspell"; my @keys = sort keys %wordsToCheck; my $count = @keys; my $all = "@keys"; LogInfo "Words to check: $count"; my @result = `echo "$all" | /usr/bin/aspell -l en -a -p ./aspell.en.pws 2>&1`; for my $res (@result) { if ($res =~ /error/i) { LogError "aspell error: $res"; last; } next if not $res =~ /^\s*&\s*(\S+)/; my $word = $1; chomp $res; my $where = "??"; if (not defined $wordsToCheck{$word}) { for my $k (@keys) { if ($k =~ /(^$word|$word$)/) { $where = $wordsToCheck{$k}; last; } $where = $wordsToCheck{$k} if ($k =~ /$word/); } } else { $where = $wordsToCheck{$word}; } if ($word =~ /^[A-Z0-9]{2,}$/) { LogWarning "Word '$word' is misspelled or is acronym, add to acronyms.txt? $where"; } else { LogWarning "Word '$word' is misspelled $where"; } } } sub CheckDoxygenStyle { my ($header, $line, $n) = @_; return if (not $line =~ /\@(\w+)/); my $mark = $1; if ($mark eq "file" and not $line =~ /\@file\s+($header)/) { LogWarning "\@file should match format: sai\\w+.h: $header $n:$line"; return; } if ($mark eq "brief" and not $line =~ /\@brief\s+[A-Z]/) { LogWarning "\@brief should start with capital letter: $header $n:$line"; return; } if ($mark eq "return" and not $line =~ /\@return\s+(#SAI_|[A-Z][a-z])/) { LogWarning "\@return should start with #: $header $n:$line"; return; } if ($mark eq "param" and not $line =~ /\@param\[(in|out|inout)\] (\.\.\.|[a-z]\w+)\s+([A-Z]\w+)/) { LogWarning "\@param should be in format \@param[in|out|inout] [a-z]\\w+ [A-Z]\\w+: $header $n:$line"; return; } if ($mark eq "defgroup" and not $line =~ /\@defgroup SAI\w* SAI - \w+/) { LogWarning "\@defgroup should be in format \@defgroup SAI\\w* SAI - \\w+: $header $n:$line"; return; } } sub ExtractComments { my $input = shift; my @comments = (); # good enough comments extractor C/C++ source while ($input =~ m!(".*?")|//.*?[\r\n]|/\*.*?\*/!s) { $input = $'; push @comments,$& if not $1; } return @comments; } sub CheckHeaderLicense { my ($data, $file) = @_; my $header = <<_END_ /** * Copyright (c) 20XX Microsoft Open Technologies, Inc. * * 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 * * THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR * CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT * LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS * FOR A PARTICULAR PURPOSE, MERCHANTABILITY OR NON-INFRINGEMENT. * * See the Apache Version 2.0 License for specific language governing * permissions and limitations under the License. * * Microsoft would like to thank the following companies for their review and * assistance with these files: Intel Corporation, Mellanox Technologies Ltd, * Dell Products, L.P., Facebook, Inc., Marvell International Ltd. _END_ ; $header =~ s/^( \*|\/\*\*)/#/gm if $data =~ /^#/; # remove first line (shell definition) $data =~ s/^#.+\n// if $data =~ /^#/; my $is = substr($data, 0, length($header)); $is =~ s/ 20\d\d / 20XX /; return if $is eq $header; LogWarning "Wrong header in $file, is:\n$is\n should be:\n\n$header"; } sub CheckStatsFunction { my ($fname,$fn,$fnparams) = @_; return if $fname eq "sai_clear_port_all_stats_fn"; # exception return if $fname eq "sai_get_tam_snapshot_stats_fn"; # exception return if $fname eq "sai_bulk_object_get_stats_fn"; # exception return if $fname eq "sai_bulk_object_clear_stats_fn"; # exception if (not $fname =~ /^sai_((get|clear)_(\w+)_stats|get_\w+_stats_ext)_fn$/) { LogWarning "wrong stat function name: $fname, expected: sai_(get|clear)_\\w+_stats(_ext)?_fn"; } if (not $fnparams =~ /^\w+_id number_of_counters counter_ids( (mode )?counters)?$/) { if (not $fnparams =~ /^\w+_entry number_of_counters counter_ids( (mode )?counters)?$/) { LogWarning "invalid stat function $fname params names: $fnparams"; } } my @paramtypes = $fn =~ /_(?:In|Out|Inout)_\s*(.+?)\s*(?:\w+?)\s*[,\)]/gis; my $ptypes = "@paramtypes"; if (not $ptypes =~ /^sai_object_id_t uint32_t const sai_stat_id_t \*( (sai_stats_mode_t )?uint64_t \*)?$/) { if (not $ptypes =~ /^const \w+_entry_t \* uint32_t const sai_stat_id_t \*( (sai_stats_mode_t )?uint64_t \*)?$/) { LogWarning "invalid stat function $fname param types: $ptypes"; } } } sub CheckFunctionsParams { # # make sure that doxygen params match function params names # my ($data, $file) = @_; my $doxygenCommentPattern = '/\*\*((?:(?!\*/).)*?)\*/'; my $fnTypeDefinition = 'typedef\s*\w+[^;]+?(\w+_fn)\s*\)([^;]+?);'; my $globalFunction = 'sai_\w+\s*(sai_\w+)[^;]*?\(([^;]+?);'; while ($data =~ m/$doxygenCommentPattern\s*(?:$fnTypeDefinition|$globalFunction)/gis) { my $comment = $1; my $fname = $2; my $fn = $3; $fname = $4 if defined $4; $fn = $5 if defined $5; my @params = $comment =~ /\@param\[\w+]\s+(\.\.\.|\w+)/gis; my @fnparams = $fn =~ /_(?:In|Out|Inout)_.+?(\.\.\.|\w+)\s*[,\)]/gis; for my $p (@params) { LogWarning "param $p in $fname should not be prefixed sai_" if $p =~ /sai_/; } my $params = "@params"; my $fnparams = "@fnparams"; if ($params ne $fnparams) { LogWarning "not matching params in function $fname: $file"; LogWarning " doxygen '$params' vs code '$fnparams'"; } if ("@params" =~ /[A-Z]/) { LogWarning "params should use small letters only '@params' in $fname: $file"; } # exceptions next if $fname eq "sai_remove_all_neighbor_entries_fn"; next if $fname eq "sai_switch_register_write_fn"; next if $fname eq "sai_switch_register_read_fn"; next if $fname eq "sai_switch_mdio_write_fn"; next if $fname eq "sai_switch_mdio_read_fn"; next if $fname eq "sai_switch_mdio_cl22_write_fn"; next if $fname eq "sai_switch_mdio_cl22_read_fn"; my @paramsFlags = lc($comment) =~ /\@param\[(\w+)]/gis; my @fnparamsFlags = lc($fn) =~ /_(\w+)_.+?(?:\.\.\.|\w+)\s*[,\)]/gis; if (not "@paramsFlags" eq "@fnparamsFlags") { LogWarning "params flags not match ('@paramsFlags' vs '@fnparamsFlags') in $fname: $file"; } next if not $fname =~ /_fn$/; # below don't apply for global functions if (not $fnparams =~ /^(\w+)(| attr| attr_count attr_list| switch_id attr_count attr_list)$/ and not $fname =~ /_(stats|stats_ext|notification)_fn$|^sai_(send|allocate|free|recv|bulk)_|^sai_meta/) { LogWarning "wrong param names: $fnparams: $fname"; LogWarning " expected: $params[0](| attr| attr_count attr_list| switch_id attr_count attr_list)"; } if ($fname =~ /^sai_(get|set|create|remove)_(\w+?)(_attribute)?(_stats|_stats_ext)?_fn/) { my $pattern = $2; my $first = $params[0]; if ($pattern =~ /_entry$/) { $pattern = "${pattern}_id|${pattern}"; } else { $pattern = "${pattern}_id"; } if (not $first =~ /^$pattern$/) { LogWarning "first param should be called ${pattern} but is $first in $fname: $file"; } } if ($fname =~ /^sai_\w+_stats_/) { CheckStatsFunction($fname,$fn,$fnparams); } } } sub CheckNonDoxygenComments { my ($data, $file) = @_; while ($data =~ m%( */\*[^\*](?:(?!\*/).)*?\*/)(\n *(\w+))?%gis) { my $comment = $1; my $stick = $3; if (($comment =~ /\W\@\w+/is) or defined $stick) { LogWarning "candidate for doxygen comment in $file:\n$comment"; LogWarning "comment sticked to $stick" if defined $stick; } } } sub CheckDoxygenCommentFormating { my ($data, $file) = @_; while ($data =~ m%/\*\*(?:(?!\*/).)*?(\*/\n[\n]+(\s*[a-z][^\n]*))%gis) { LogWarning "empty line between doxygen comment and definition: $file: $2"; } while ($data =~ m%( *)(/\*\*(?:(?!\*/).)*?\*/)%gis) { my $spaces = $1 . " "; my $comment = $2; next if $comment =~ m!^/\*\*.*\*/$!; # single line comment my @lines = split/\n/,$comment; my $first = shift @lines; my $last = pop @lines; if (not $first =~ m!^\s*/..$!) { LogWarning "first line doxygen comment should be with '/**': $file: $first"; next; } if (not $last =~ m!^\s*\*/$!) { LogWarning "last line doxygen comment should be '*/': $file: $last"; next; } if (not $lines[0] =~ m!\* (\@|Copyright )!) { LogWarning "first doxygen line should contain \@ tag $file: $lines[0]"; } if ($lines[$#lines] =~ m!^\s*\*\s*$!) { LogWarning "last doxygen line should not be empty $file: $lines[$#lines]"; } for my $line (@lines) { if (not $line =~ m!^\s*\*( |$)!) { LogWarning "multiline doxygen comments should start with '* ': $file: $line"; } if (not $line =~ /^$spaces\*/) { LogWarning "doxygen comment has invalid ident: $file: $line"; } } next; # disabled for now since it generates too much changes $comment =~ s!^ *(/\*\*|\*/|\* *)!!gm; if ($comment =~ m!\@brief\s+(.+?)\n\n!s) { my $brief = $1; if (not $brief =~ /\.$/) { LogWarning "brief should end with dot $file: $brief"; } } my @n = split/^\@\S+ /m,$comment; } while($data =~ m!(([^\n ])+\n */\*\*.{1,30}.+?\n)!isg) { next if $2 eq "{"; LogWarning "doxygen comment must be preceded with blank line: $file:\n$1"; } } sub IsObjectName { my $ot = shift; return 1 if defined $main::OBJTOAPIMAP{$ot} or defined $main::OBJTOAPIMAP{uc("SAI_OBJECT_TYPE_".$ot)}; return 0; } sub CheckFunctionNaming { my ($header, $n, $line) = @_; return if not $line =~ /^\s*sai_(\w+)_fn\s+(\w+)\s*;/; my $typename = $1; my $name = $2; my @listex = qw( allocate_hostif_packet flush_fdb_entries free_hostif_packet profile_get_next_value profile_get_value recv_hostif_packet remove_all_neighbor_entries send_hostif_packet switch_mdio_read switch_mdio_write switch_mdio_cl22_read switch_mdio_cl22_write switch_register_read switch_register_write); my $REG = "(" . (join"|",@listex) . ")"; if ($name =~ /^$REG$/) { # ok } elsif ($name =~ /^(get|clear)_(\w+?)_(all_)?stats(_ext)?$/) { LogWarning "not object name $2 in $name" if not IsObjectName($2); } elsif ($name =~ /^(create|remove|get|set)_(\w+?)(_attribute)?$/) { my $n = $2; $n =~ s/_entries$/_entry/ if $typename =~ /^bulk/; $n =~ s/s$// if $typename =~ /^bulk/; LogWarning "not object name $n in $name" if not IsObjectName($n); } else { LogWarning "Line not matching any name pattern: $line"; } if ($typename ne $name and not $typename =~ /^bulk_/) { LogWarning "function not matching $typename vs $name in $header:$n:$line"; } if (not $name =~ /^(create|remove|get|set)_\w+?(_attribute)?$|^clear_\w+_stats$/) { # exceptions return if $name =~ /^$REG$/; LogWarning "function not follow convention in $header:$n:$line"; } } sub CheckQuadApi { my ($data, $file) = @_; return if not $data =~ m!(sai_\w+_api_t)(.+?)\1;!igs; my $apis = $2; my @fns = $apis =~ /sai_(\w+)_fn/g; # this function forces order of existing and new added apis in api struct my $order = ""; for my $function (@fns) { my $f = $function; $f =~ s/remove_all_neighbor_entries/X/; $f =~ s/clear_port_all_stats/X/; $f =~ s/^create_\w+/c/; $f =~ s/^remove_\w+/r/; $f =~ s/^set_\w+_attribute/s/; $f =~ s/^get_\w+_attribute/g/; $f =~ s/^get_\w+_stats$/0/; $f =~ s/^get_\w+_stats_ext/1/; $f =~ s/^clear_\w+_stats/2/; $f =~ s/^bulk_object_create/C/; $f =~ s/^bulk_object_remove/R/; $f =~ s/^bulk_object_set_attribute/S/; $f =~ s/^bulk_object_get_attribute/G/; $f =~ s/^bulk_create_\w+/C/; $f =~ s/^bulk_remove_\w+/R/; $f =~ s/^bulk_set_\w+_attribute/S/; $f =~ s/^bulk_get_\w+_attribute/G/; $f = "X" if length $f != 1; $order .= $f; } $order =~ s/crsg012/t/g; # order should be: create,remove,set,get,get_stats,get_stats_ext,clear_stats $order =~ s/crsg/q/g; # order should be: create,remove,set,get $order =~ s/CRSG/Q/g; # order should be: bulk_create,bulk_remove,bulk_set,bulk_get $order =~ s/012/s/g; # order should be: get_stats,get_stats_ext,clear_stats $order =~ s/CR/E/g; # order should be: bulk_create,bulk_remove $order =~ s/SG/T/g; # order should be: bulk_set,bulk_get $order =~ s/X+/X/g; # order should be: any non quad and non stats api if (not $order =~ /^[tqQsETX]*$/) { LogWarning "Wrong api order: $order"; LogWarning "$apis"; } my $fn = join" ",@fns; my @quad = split/\bcreate_/,$fn; for my $q (@quad) { next if $q eq ""; if (not $q =~ /(\w+) remove_\1 set_\1_attribute get_\1_attribute( |$)/) { LogWarning "quad api must be in order: create remove set get: $file: $q"; } } } sub CheckSwitchKeys { my ($data, $file) = @_; my $keycount = $1 if $data =~ /#define\s+SAI_SWITCH_ATTR_MAX_KEY_COUNT\s+(\d+)/s; my $count = 0; while ($data =~ m!#define\s+SAI_KEY_(\w+)\s+"SAI_(\w+)"!gs) { if ($1 ne $2) { LogWarning "SAI_(KEY_)$1 should match SAI_$2"; } $count++; } if ($count != $keycount) { LogWarning "SAI_SWITCH_ATTR_MAX_KEY_COUNT is $keycount, but found only $count keys"; } } sub CheckStructAlignment { my ($data, $file) = @_; # return if $file eq "saitypes.h"; while ($data =~ m!typedef\s+(?:struct|union)\s+_(sai_\w+)(.+?)}\s*(\w+);!igs) { my $struct = $1; my $inner = $2; my $enddef = $3; if ($struct ne $enddef) { LogError "expected same names for _$struct $enddef"; } # we use space in regex since \s will capture \n $inner =~ m/^( *)(\w.+\s+)(\w+)\s*;$/im; my $spaces = $1; my $inside = $2; my $name = $3; while ($inner =~ m/^( *)(\w.+\s+)(\w+)\s*;$/gim) { my $itemname = $2; if ($1 ne $spaces or (length($2) != length($inside) and $struct =~ /_api_t/)) { LogError "$struct items has invalid column ident: $file: $itemname"; } } } } sub GetAcronyms { # load acronyms list from file my $filename = "acronyms.txt"; open FILE, $filename or die "Couldn't open file $filename: $!"; my @acronyms; while (<FILE>) { chomp; my $line = $_; next if $line =~ /(^#|^$)/; if (not $line =~ /^([A-Z0-9]{2,})(\s+-\s+(.+))?$/) { LogWarning "invalid line in $filename: $line"; next; } my $acronym = $1; my $definition = $3; push @acronyms,$acronym; } close FILE; my $prev = ""; for my $acr (@acronyms) { LogWarning "Acronyms are not sorted: $prev, $acr" if ($prev cmp $acr) > 0; $prev = $acr; } return @acronyms; } sub CheckMetadataSourceFiles { my @files = GetMetadataSourceFiles(); for my $file (@files) { # skip auto generated headers next if $file eq "saimetadata.h"; next if $file eq "saimetadata.c"; next if $file eq "saimetadatatest.c"; next if $file eq "saimetadatasize.h"; next if $file eq "sai_rpc_server.cpp"; next if $file =~ /swig|wrap/; my $data = ReadHeaderFile($file); CheckHeaderLicense($data, $file); LogError "missing declaration '\@file $file'" if (not $data =~ /[*#]\s+\@file\s+\Q$file\E$/m); my @lines = split/\n/,$data; my $n = 0; for my $line(@lines) { $n++; LogWarning "found trailing spaces in $file:$n: $line" if $line =~ /\s+$/; $line =~ s/\t+/ /g if $file =~ /Makefile/; if ($line =~ /[^\x20-\x7e]/) { LogWarning "line contains non ascii characters $file:$n: $line"; } } } } sub CheckInOutParams { my ($header, $n, $line) = @_; return if not $line =~ / _(In|Out|Inout)_ /; return if $header eq "saiserialize.h"; return if $header eq "saimetadatalogger.h"; if (not $line =~ / _(In|Out|Inout)_ (const )?(\w+)( \*| \*\*| )(\w+)[\),]/) { LogError "not matching param line: $header:$n: $line"; return; } my $inout = $1; my $const = (not defined $2) ? "" : "const"; my $type = $3; my $ptr = $4; my $param = $5; if ($type eq "sai_object_id_t" and not $line =~ /_In_ sai_object_id_t \w+|_In_ const sai_object_id_t \*\w+|_Out_ sai_object_id_t \*\w+/) { LogError "wrong in/out param mix: $header:$n:$line"; } if ($type eq "sai_attribute_t" and not $line =~ /_In_ const sai_attribute_t \*\*?\w+|_Inout_ sai_attribute_t \*\*?\w+/) { return if $header eq "saihostif.h"; LogError "wrong in/out param mix: $header:$n:$line"; } # TODO we should know if type is simple or struct/union return if $line =~ /_In_ \w+ \w+/ and $const eq ""; # non const types without pointer should be In return if $line =~ /_Inout_ \w+ \*\w+/ and $const eq ""; # non const types with pointer should be Inout return if $line =~ /_Out_ \w+ \*\w+/ and $const eq ""; # non const types with pointer should be Out return if $line =~ /_In_ const \w+ \*\*?\w+/; # const types with pointer should be In return if $line =~ /_In_ const \w+ \w+/; # const types without pointer return if $line =~ /_Out_ const char \*\*\w+/; return if $line =~ /_Out_ void \*\*\w+/; return if $line =~ /_Inout_ sai_attribute_t \*\*\w+/; LogWarning "Not supported param prefixes, FIXME: $header:$n $line"; } sub CheckComment { my ($data, $header) = @_; my @lines = split/\n/,$data; return if $data =~ /\s*\s*\@(file|defgroup|}|def |extraparam|passparam)/; my $c = ""; for my $line (@lines) { next if $line =~ m!^/\*\*|\*/!; $line =~ s/\@note|\@par //g; if ($line =~ /^\s*\*\s+\@warning/) { $c .= "W"; next } if ($line =~ /^\s*\*\s+\@param/) { $c .= "P"; next } if ($line =~ /^\s*\*$/) { $c .= " "; next } if ($line =~ /^\s*\*\s+\@brief/) { $c .= "B"; next } if ($line =~ /^\s*\*\s+\@return/) { $c .= "R"; next } if ($line =~ /^\s*\*\s+\@/) { $c .= "@"; next } $c .= "x"; } return if $c eq ""; $c =~ s/x+/x/g; $c =~ s/Px/P/g; $c =~ s/P( P)+/P/g; $c =~ s/P+/P/g; $c =~ s/Bx/B/g; $c =~ s/Rx/R/g; $c =~ s/x( x)+/x/g; $c =~ s/\@+/@/g; return if $c =~ /^B( W)?( x)?( \@)?( P)?( R)?$/; LogWarning "empty line required between each below elements:"; LogWarning "desired elemen order: \@brief \@warning? description? \@attributes? \@params? \@return?"; LogWarning "invalid spacing ($c) on $header:\n$data\n"; } sub CheckDoxygenSpacing { my ($data, $header) = @_; my @comments = ExtractComments($data, $header); for my $com (@comments) { next if $com =~ m!^//!; next if not $com =~ m!^/\*\*!; CheckComment($com, $header); } } sub GetWordsFromSources { my $wordsToCheck = shift; my @sources = GetMetaSourceFiles(); my @acronyms = GetAcronyms(); my @spellExceptions = qw/ IPv4 IPv6 /; my %exceptions = map { $_ => $_ } @spellExceptions; my %ac = (); $ac{$_} = 1 for @acronyms; for my $src (sort @sources) { next if $src =~ /saimetadata.c/; next if $src =~ /saimetadatatest.c/; next if $src =~ /saiswig/; next if $src =~ /sai_rpc_server.cpp/; my $data = ReadHeaderFile($src); my @comments = ExtractComments($data); for my $comment(@comments) { my @lines = split/\n/,$comment; for my $line (@lines) { while ($line =~ /\b([a-z0-9]+)\b/ig) { my $pre = $`; my $post = $'; my $word = $1; next if $word =~ /xFF/; next if defined $ac{$word}; next if defined $wordsToCheck->{$word}; next if defined $exceptions{$word}; $wordsToCheck->{$word} = $src; } } } } } sub CheckHeadersStyle { # # Purpose of this check is to find out # whether header files are correctly formated # # Wrong formating includes: # - multiple empty lines # - double spaces # - wrong spacing idient # # we could put that to local dictionary file my @acronyms = GetAcronyms(); my @spellExceptions = qw/ CRC32 IPv4 IPv6 BGPv6 6th 0xFF /; my %exceptions = map { $_ => $_ } @spellExceptions; my %wordsToCheck = (); my %wordsChecked = (); CheckMetadataSourceFiles(); my @headers = GetHeaderFiles(); my @metaheaders = GetMetaHeaderFiles(); my @exheaders = GetExperimentalHeaderFiles(); @headers = (@headers, @metaheaders, @exheaders); for my $header (@headers) { next if $header eq "saimetadata.h"; # skip auto generated header next if $header eq "saimetadatasize.h"; # skip auto generated header my $data = ReadHeaderFile($header); my $oncedef = uc ("__${header}_"); $oncedef =~ s/\./_/g; my $oncedefCount = 0; CheckHeaderLicense($data, $header); CheckFunctionsParams($data, $header); CheckDoxygenCommentFormating($data, $header); CheckQuadApi($data, $header); CheckStructAlignment($data, $header); CheckNonDoxygenComments($data, $header); CheckSwitchKeys($data, $header) if $header eq "saiswitch.h"; CheckDoxygenSpacing($data, $header); my @lines = split/\n/,$data; my $n = 0; my $empty = 0; my $emptydoxy = 0; for my $line (@lines) { $n++; CheckFunctionNaming($header, $n, $line); CheckInOutParams($header, $n, $line); $oncedefCount++ if $line =~ /\b$oncedef\b/; # detect multiple empty lines if ($line =~ /^$/) { $empty++; if ($empty > 1) { LogWarning "header contains two empty lines one after another $header $n"; } } else { $empty = 0 } # detect multiple empty lines in doxygen comments if ($line =~ /^\s+\*\s*$/) { $emptydoxy++; if ($emptydoxy > 1) { LogWarning "header contains two empty lines in doxygen $header $n"; } } else { $emptydoxy = 0 } if ($line =~ /^\s+\* / and not $line =~ /\*( {4}| {8}| )[^ ]/) { LogWarning "not expected number of spaces after * (1,4,8) $header $n:$line"; } if ($line =~ /\*\s+[^ ].* / and not $line =~ /\* \@(brief|file|note)/) { if (not $line =~ /const.+const\s+\w+;/ and not $line =~ m!\\$!) { LogWarning "too many spaces after *\\s+ $header $n:$line"; } } if ($line =~ /(typedef|{|}|_In\w+|_Out\w+)( [^ ].* | )/ and not $line =~ /typedef\s+u?int/i and not $line =~ m!\\$!) { LogWarning "too many spaces $header $n:$line"; } if ($line =~ m!/\*\*! and not $line =~ m!/\*\*\s*$! and not $line =~ m!/\*\*.+\*/!) { LogWarning "multiline doxygen comment should start '/**' $header $n:$line"; } if ($line =~ m![^ ]\*/!) { LogWarning "coment is ending without space $header $n:$line"; } if ($line =~ /^\s*sai_(\w+)_fn\s+(\w+);/) { # make struct function members to follow convention LogWarning "$2 should be equal to $1" if (($1 ne $2) and not($1 =~ /^bulk/)) } if ($line =~ /_(?:In|Out)\w+\s+(?:sai_)?uint32_t\s*\*?(\w+)/) { my $param = $1; my $pattern = '^(attr_count|object_count|number_of_counters|count|u32|device_addr|start_reg_addr|number_of_registers|reg_val)$'; if (not $param =~ /$pattern/) { LogWarning "param $param should match $pattern $header:$n:$line"; } } if ($line =~ /typedef.+_fn\s*\)/ and not $line =~ /typedef( \S+)+ \(\*\w+_fn\)\(/) { LogWarning "wrong style typedef function definition $header:$n:$line"; } if ($line =~ / ([.,:;)])/ and not $line =~ /\.(1D|1Q|\.\.)/) { LogWarning "space before '$1' : $header:$n:$line"; } if ($line =~ / \* / and not $line =~ /^\s*\* / and not $line =~ /^#define/) { LogWarning "floating star $header:$n:$line"; } if ($line =~ /}[^ ]/) { LogWarning "no space after '}' $header:$n:$line" if (not $line =~ /^\s*\* /); } if ($line =~ /_[IO].+\w+\*\*? /) { LogWarning "star should be next to param name $header:$n:$line"; } if ($line =~ /_In_ .+\*/ and not $line =~ /_In_ const/) { LogWarning "pointer input parameters should be const $header:$n:$line"; } if ($line =~ /[^ ]\s*_(In|Out|Inout)_/ and not $line =~ /^#define/) { LogWarning "each param should be in separate line $header:$n:$line"; } if ($line =~ m!/\*\*\s+[a-z]!) { LogWarning "doxygen comment should start with capital letter: $header:$n:$line"; } if ($line =~ /sai_\w+_statistics_fn/) { LogWarning "statistics should use 'stats' to follow convention $header:$n:$line"; } if ($line =~ /^\s*(SAI_\w+)\s*=\s*(.*)$/) { my $init = $2; if ($init =~ m!^(0x\w+|SAI_\w+|SAI_\w+ \+ \d+|SAI_\w+ \+ 0x[0-9a-f]{1,8}|SAI_\w+ \+ SAI_\w+|\d+|\(?\d+ << \d+\)?),?\s*(/\*\*.*\*/)?$!) { # supported initializers for enum: # - 0x00000000 (hexadecimal number) # - 0 (decimal number) # - SAI_... (other SAI enum) # - n << m (flags shifted) # - SAI_.. + SAI_.. (sum of SAI enums) # - SAI_.. + 0x00 (sum of SAI and hexadecimal number) # - SAI_.. + 0 (sum of SAI and decimal number) } else { LogWarning "unsupported initializer on enum: $line"; } } if ($line =~ /^\s*SAI_\w+\s*=\s*+0x(\w+)(,|$)/ and length($1) != 8) { LogWarning "enum number '0x$1' should have 8 digits $header:$n:$line"; } if ($line =~ /^\s*SAI_\w+(\s*)=(\s*)/ and ($1 eq "" or $2 eq "")) { LogWarning "space is required before and after '=' $header:$n:$line"; } if ($line =~ /#define\s*(\w+)/ and $header ne "saitypes.h") { my $defname = $1; if (not $defname =~ /^(SAI_|__SAI)/) { LogWarning "define should start with SAI_ or __SAI: $header:$n:$line"; } } if ($line =~ /\s+$/) { LogWarning "line ends in whitespace $header $n: $line"; } if ($line =~ /[^\x20-\x7e]/) { LogWarning "line contains non ascii characters $header $n: $line"; } if ($line =~ /typedef .+?\(\s*\*\s*(\w+)\s*\)/) { my $fname = $1; if (not $fname =~ /^sai_\w+_fn$/) { LogWarning "all function declarations should be in format sai_\\w+_fn $header $n: $line"; } } my $prev = $lines[$n-2]; if ($line =~ /\*\s*\@\w+/ and $prev =~ /\@brief/) { LogWarning "missing empty line before $header $n: $line"; } if ($line =~ /==|SAI_\w+/ and $prev =~ /\@(validonly|condition)/) { LogWarning "merge with previous line: $header $n: $line"; } if ($line =~ /\@type/ and $prev =~ /^\s*\*./) { LogWarning "missing empty line before: $header $n: $line"; } if ($line =~ /_(In|Out|Inout)_.+(\* | \* )/) { LogWarning "move * to the right of parameter: $header $n: $line"; } if ($line =~ /\*.*SAI_.+(==|!=)/ and not $line =~ /\@(condition|validonly)/) { if (not $line =~ /(condition|validonly|valid when|only when)\s+SAI_/i) { LogWarning "condition should be preceded by 'valid when' or 'only when': $header $n: $line"; } } if ($line =~ /SAI_\w+ \s+=\s+(0x|S)/) { LogWarning "too many spaces before '=' $header:$n: $line" } if ($line =~ /__/ and not $line =~ /^#.+__SAI\w*_H_|VA_ARGS|BOOL_DEFINED/) { LogWarning "double underscore detected: $header $n: $line"; } if ($line eq "" and $prev =~ /{/) { LogWarning "empty line after '$prev': $header:$n: $line"; } my $pattern = join"|", @acronyms; while ($line =~ /\b($pattern)\b/igp) { my $pre = $`; my $post = $'; # force special word to be capital my $word = $1; next if $word =~ /^($pattern)$/; next if $word =~ /^(an|An)$/; # exception, can be word and acronym next if $line =~ /$word.h/; next if not $line =~ /\*/; # must contain star, so will be comment next if "$pre$word" =~ m!http://$word$!; next if ($line =~ /\@param\[\w+\]\s+$word /); # skip word if word is param name LogWarning "Word '$word' should use capital letters $header $n:$line"; } # perform aspell checking (move to separate method) if ($line =~ m!^\s*(\*|/\*\*)!) { while ($line =~ /\b([a-z0-9']+)\b/ig) { my $pre = $`; my $post = $'; my $word = $1; next if $word =~ /^($pattern)$/; # capital words next if ($line =~ /\@validonly\s+\w+->\w+/); # skip valionly code next if ($line =~ /\@passparam\s+\w+/); # skip passparam next if ($line =~ /\@extraparam\s+\w+/); # skip extraparam next if ($line =~ /\@param\[\w+\]\s+$word /); # skip word if word is param name # look into good and bad words hash to speed things up next if defined $exceptions{$word}; next if $word =~ /^sai\w+/i; next if $word =~ /0x\S+/; next if "$pre$word" =~ /802.\d+\w+/; next if defined $wordsChecked{$word}; $wordsChecked{$word} = 1; $wordsToCheck{$word} = "$header $n:$line"; } } if ($line =~ /#include\s*\"sai/ and not $header =~ /^sai(|extensions|metadatautils).h$/) { # TODO we should dedice later whether use <> or "" on all includes to make it consistent LogWarning "include should use <> brackets on: $header:$n:$line"; } if ($line =~ /typedef\s*(enum|struct|union).*{/) { LogWarning "move '{' to new line in typedef $header $n:$line"; } if ($line =~ /^[^*]*union/ and not $line =~ /union _\w+\b/) { LogError "all unions must be named $header $n:$line"; } CheckDoxygenStyle($header, $line, $n); next if $line =~ /^ \*($|[ \/])/; # doxygen comment next if $line =~ /^$/; # empty line next if $line =~ /^typedef /; # type definition next if $line =~ /^sai_status_t sai_\w+\(/; # return codes next if $line =~ /^sai_object\w+_t sai_\w+\(/; # return codes next if $line =~ /^int sai_\w+\($/; # methods returning int next if $line =~ /^extern /; # extern in metadata next if $line =~ /^[{}#\/]/; # start end of struct, define, start of comment next if $line =~ /^ {8}(_In|_Out|\.\.\.)/; # function arguments next if $line =~ /^ {4}(sai_)/i; # sai struct entry or SAI enum next if $line =~ /^ {4}\/\*/; # doxygen start next if $line =~ /^ {8}\/\*/; # doxygen start next if $line =~ /^ {5}\*/; # doxygen comment continue next if $line =~ /^ {8}sai_/; # union entry next if $line =~ /^ {4}union/; # union next if $line =~ /^ {4}[{}]/; # start or end of union next if $line =~ /^ {4}(u?int)/; # union entries next if $line =~ /^ {4}(char|bool)/; # union entries next if $line =~ /^ {8}bool booldata/; # union bool next if $line =~ /^ {4}(true|false)/; # bool definition next if $line =~ /^ {4}(const|size_t|else)/; # const in meta headers next if $line =~ /^(void|bool) /; # function return next if $line =~ m![^\\]\\$!; # macro multiline next if $line =~ /^ {4}(\w+);$/; # union entries next if $line =~ /^union _sai_\w+ \{/; # union entries LogWarning "C++ comment in ANSI C header: $header $n:$line" if $line =~ /\/\//; LogWarning "Header doesn't meet style requirements (most likely ident is not 4 or 8 spaces) $header $n:$line"; } if ($oncedefCount != 3) { LogWarning "$oncedef should be used 3 times in header, but used $oncedefCount"; } } GetWordsFromSources(\%wordsToCheck); RunAspell(\%wordsToCheck) if not defined $main::optionDisableAspell; } BEGIN { our @ISA = qw(Exporter); our @EXPORT = qw/ CheckHeadersStyle GetAcronyms /; } 1;