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;