diff --git a/checkFile.awk b/checkFile.awk index efb5b34..420c15e 100644 --- a/checkFile.awk +++ b/checkFile.awk @@ -6,7 +6,7 @@ function error(fname, linum, msg) { printf "%s: %d: %s\n", fname, linum, msg; if (0) print; # for debug } -function matchFile(fname) { +function _matchFile(fname) { return fname ~ "/mondrian/" \ || fname ~ "/org/olap4j/" \ || fname ~ "/aspen/" \ @@ -15,28 +15,35 @@ function matchFile(fname) { || fname ~ "/com/sqlstream/" \ || !lenient; } -function isCpp(fname) { +function _isCpp(fname) { return fname ~ /\.(cpp|h)$/; } -function isJava(fname) { - return !isCpp(fname); +function _isJava(fname) { + return fname ~ /\.(java|jj)$/; } function push(val) { - switchStack[switchStackLen++] = val; + switchStack[switchStackLen++] = val; } function pop() { - --switchStackLen - val = switchStack[switchStackLen]; - delete switchStack[switchStackLen]; - return val; + --switchStackLen + val = switchStack[switchStackLen]; + delete switchStack[switchStackLen]; + return val; } BEGIN { - # pre-compute regexp for single-quoted strings + # pre-compute regexp for quotes, linefeed apos = sprintf("%c", 39); + quot = sprintf("%c", 34); lf = sprintf("%c", 13); pattern = apos "(\\" apos "|[^" apos "])" apos; if (0) printf "maxLineLength=%s lenient=%s\n", maxLineLength, lenient; } +FNR == 1 { + fname = FILENAME; + matchFile = _matchFile(fname); + isCpp = _isCpp(fname); + isJava = _isJava(fname); +} { if (previousLineEndedInCloseBrace > 0) { --previousLineEndedInCloseBrace; @@ -76,34 +83,60 @@ BEGIN { } # mask out // comments gsub(/\/\/.*$/, "// comment", s); + # line starts with string or plus? + if (s ~ /^ *string/ \ + && s !~ /)/) + { + stringCol = index(s, "string"); + } else if (s ~ /^ *[+] string/) { + if (stringCol != 0 && index(s, "+") != stringCol) { + error(fname, FNR, "String '+' must be aligned with string on line above"); + } + } else if (s ~ /comment/) { + # in comment; string target carries forward + } else { + stringCol = 0; + } } / $/ { error(fname, FNR, "Line ends in space"); } /[\t]/ { - if (matchFile(fname)) { + if (matchFile) { error(fname, FNR, "Tab character"); } } +/[\r]/ { + if (matchFile) { + error(fname, FNR, "Carriage return character (file is in DOS format?)"); + } +} +{ + # Rules beyond this point only apply to Java and C++. + if (!isCpp && !isJava) { + next; + } +} + /^$/ { - if (matchFile(fname) && previousLineEndedInOpenBrace) { + if (matchFile && previousLineEndedInOpenBrace) { error(fname, FNR, "Empty line following open brace"); } } /^ +}( catch| finally| while|[;,)])/ || /^ +}$/ { - if (matchFile(fname) && previousLineWasEmpty) { + if (matchFile && previousLineWasEmpty) { error(fname, FNR - 1, "Empty line before close brace"); } } s ~ /\.*;$/ { - if (!matchFile(fname)) {} + if (!matchFile) {} else { error(fname, FNR, "if followed by statement on same line"); } } s ~ /\<(if) *\(/ { - if (!matchFile(fname)) { + if (!matchFile) { } else if (s !~ /\<(if) /) { error(fname, FNR, "if must be followed by space"); } else if (s ~ / else if /) { @@ -113,7 +146,7 @@ s ~ /\<(if) *\(/ { } } s ~ /\<(while) *\(/ { - if (!matchFile(fname)) { + if (!matchFile) { } else if (s !~ /\<(while) /) { error(fname, FNR, "while must be followed by space"); } else if (s ~ /} while /) { @@ -122,7 +155,7 @@ s ~ /\<(while) *\(/ { } } s ~ /\<(for|switch|synchronized|} catch) *\(/ { - if (!matchFile(fname)) {} + if (!matchFile) {} else if (s !~ /^( )*(for|switch|synchronized|} catch)/) { error(fname, FNR, "for/switch/synchronized/catch must be correctly indented"); } else if (s !~ /\<(for|switch|synchronized|} catch) /) { @@ -138,10 +171,10 @@ s ~ /\<(if|while|for|switch)\>/ { gsub(/[^(]/, "", opens); closes = s; gsub(/[^)]/, "", closes); - if (!matchFile(fname)) { + if (!matchFile) { } else if (s ~ /{( *\/\/ comment)?$/) { # lines which end with { and optional comment are ok - } else if (s ~ /{.*\\$/ && isCpp(fname)) { + } else if (s ~ /{.*\\$/ && isCpp) { # lines which end with backslash are ok in c++ macros } else if (s ~ /} while/) { # lines like "} while (foo);" are ok @@ -160,36 +193,47 @@ s ~ /\<(if|while|for|switch)\>/ { s ~ /[[:alnum:]]\(/ && s !~ /\<(if|while|for|switch|assert)\>/ { ss = s; - gsub(/.*[[:alnum:]]\(/, "(", ss); - opens = ss; - gsub(/[^(]/, "", opens); - closes = ss; - gsub(/[^)]/, "", closes); - if (length(opens) > length(closes)) { - if (s ~ /,$/) { - bras = s; - gsub(/[^<]/, "", bras); - kets = s; - gsub(/[^>]/, "", kets); - if (length(bras) > length(kets)) { - # Ignore case like 'for (Map.Entry entry : ...' - } else if (s ~ / for /) { - # Ignore case like 'for (int i = 1,{nl} j = 2; i < j; ...' + while (match(ss, /[[:alnum:]]\(/)) { + ss = substr(ss, RSTART + RLENGTH - 1); + parens = ss; + gsub(/[^()]/, "", parens); + while (substr(parens, 1, 2) == "()") { + parens = substr(parens, 3); + } + opens = parens; + gsub(/[^(]/, "", opens); + closes = parens; + gsub(/[^)]/, "", closes); + if (length(opens) > length(closes)) { + if (ss ~ /,$/) { + bras = ss; + gsub(/[^<]/, "", bras); + kets = ss; + gsub(/->/, "", kets); + gsub(/[^>]/, "", kets); + if (length(bras) > length(kets)) { + # Ignore case like 'for (Map.Entry entry : ...' + } else if (s ~ / for /) { + # Ignore case like 'for (int i = 1,{nl} j = 2; i < j; ...' + } else { + error( \ + fname, FNR, \ + "multi-line parameter list should start with newline"); + break; + } + } else if (s ~ /[;(]( *\\)?$/) { + # If open paren is at end of line (with optional backslash + # for macros), we're fine. + } else if (s ~ /@.*\({/) { + # Ignore Java annotations. } else { error( \ fname, FNR, \ - "multi-line parameter list should start with newline"); + "Open parenthesis should be at end of line (function call spans several lines)"); + break; } - } else if (s ~ /[;(]( *\\)?$/) { - # If open paren is at end of line (with optional backslash - # for macros), we're fine. - } else if (s ~ /@.*\({/) { - # Ignore Java annotations. - } else { - error( \ - fname, FNR, \ - "Open parenthesis should be at end of line (function call spans several lines)"); } + ss = substr(ss, 2); # remove initial "(" } } s ~ /\/ { @@ -210,14 +254,14 @@ s ~ /}/ { } s ~ /\<(case|default)\>/ { caseDefaultCol = match($0, /case|default/); - if (!matchFile(fname)) {} + if (!matchFile) {} else if (caseDefaultCol != switchCol) { error(fname, FNR, "case/default must be aligned with switch"); } } s ~ /\/ { - if (!matchFile(fname)) {} - else if (isCpp(fname)) {} # rule only applies to java + if (!matchFile) {} + else if (isCpp) {} # rule only applies to java else if (s !~ /^( )+(assert)/) { error(fname, FNR, "assert must be correctly indented"); } else if (s !~ /\/ { } } s ~ /\/ { - if (!matchFile(fname)) {} - else if (isCpp(fname) && s ~ /^#/) { + if (!matchFile) {} + else if (isCpp && s ~ /^#/) { # ignore macros } else if (s !~ /^( )+(return)/) { error(fname, FNR, "return must be correctly indented"); @@ -235,8 +279,8 @@ s ~ /\/ { } } s ~ /\/ { - if (!matchFile(fname)) {} - else if (isCpp(fname)) { + if (!matchFile) {} + else if (isCpp) { # cannot yet handle C++ cases like 'void foo() throw(int)' } else if (s !~ /^( )+(throw)/) { error(fname, FNR, "throw must be correctly indented"); @@ -245,53 +289,53 @@ s ~ /\/ { } } s ~ /\/ { - if (!matchFile(fname)) {} - else if (isCpp(fname) && s ~ /^# *else$/) {} # ignore "#else" + if (!matchFile) {} + else if (isCpp && s ~ /^# *else$/) {} # ignore "#else" else if (s !~ /^( )+} else (if |{$|{ *\/\/|{ *\/\*)/) { error(fname, FNR, "else must be preceded by } and followed by { or if and correctly indented"); } } s ~ /\/ { - if (!matchFile(fname)) {} + if (!matchFile) {} else if (s !~ /^( )*do {/) { error(fname, FNR, "do must be followed by space {, and correctly indented"); } } s ~ /\/ { - if (!matchFile(fname)) {} + if (!matchFile) {} else if (s !~ /^( )+try {/) { error(fname, FNR, "try must be followed by space {, and correctly indented"); } } s ~ /\/ { - if (!matchFile(fname)) {} + if (!matchFile) {} else if (s !~ /^( )+} catch /) { error(fname, FNR, "catch must be preceded by }, followed by space, and correctly indented"); } } s ~ /\/ { - if (!matchFile(fname)) {} + if (!matchFile) {} else if (s !~ /^( )+} finally {/) { error(fname, FNR, "finally must be preceded by }, followed by space {, and correctly indented"); } } match(s, /([]A-Za-z0-9()])(+|-|\*|\^|\/|%|=|==|+=|-=|\*=|\/=|>=|<=|!=|&|&&|\||\|\||^|\?|:) *[A-Za-z0-9(]/, a) { # < and > are not handled here - they have special treatment below - if (!matchFile(fname)) {} + if (!matchFile) {} # else if (s ~ /<.*>/) {} # ignore templates else if (a[2] == "-" && s ~ /\(-/) {} # ignore case "foo(-1)" else if (a[2] == "-" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e-5 else if (a[2] == "+" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e+5 else if (a[2] == ":" && s ~ /(case.*|default):$/) {} # ignore e.g. "case 5:" - else if (isCpp(fname) && s ~ /[^ ][*&]/) {} # ignore e.g. "Foo* p;" in c++ - debatable - else if (isCpp(fname) && s ~ /\" in c++ + else if (isCpp && s ~ /[^ ][*&]/) {} # ignore e.g. "Foo* p;" in c++ - debatable + else if (isCpp && s ~ /\" in c++ else { error(fname, FNR, "operator '" a[2] "' must be preceded by space"); } } match(s, /([]A-Za-z0-9() ] *)(+|-|\*|\^|\/|%|=|==|+=|-=|\*=|\/=|>=|<=|!=|&|&&|\||\|\||^|\?|:|,)[A-Za-z0-9(]/, a) { - if (!matchFile(fname)) {} + if (!matchFile) {} # else if (s ~ /<.*>/) {} # ignore templates else if (a[2] == "-" && s ~ /(\(|return |case |= )-/) {} # ignore prefix - else if (a[2] == ":" && s ~ /(case.*|default):$/) {} # ignore e.g. "case 5:" @@ -299,79 +343,99 @@ match(s, /([]A-Za-z0-9() ] *)(+|-|\*|\^|\/|%|=|==|+=|-=|\*=|\/=|>=|<=|!=|&|&&|\| else if (s ~ /-[^ ]/ && s ~ /[^A-Za-z0-9] -/) {} # ignore case "x + -1" but not "x -1" or "3 -1" else if (a[2] == "-" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e-5 else if (a[2] == "+" && s ~ /[eE][+-][0-9]/) {} # ignore e.g. 1e+5 - else if (a[2] == "*" && isCpp(fname) && s ~ /\*[^ ]/) {} # ignore e.g. "Foo *p;" in c++ - else if (a[2] == "&" && isCpp(fname) && s ~ /&[^ ]/) {} # ignore case "foo(&x)" in c++ - else if (isCpp(fname) && s ~ /\" in c++ + else if (a[2] == "*" && isCpp && s ~ /\*[^ ]/) {} # ignore e.g. "Foo *p;" in c++ + else if (a[2] == "&" && isCpp && s ~ /&[^ ]/) {} # ignore case "foo(&x)" in c++ + else if (isCpp && s ~ /\" in c++ else if (lenient && fname ~ /(fennel)/ && a[1] = ",") {} # not enabled yet else { error(fname, FNR, "operator '" a[2] "' must be followed by space"); } } +match(s, /( )(,)/, a) { + # (, < and > are not handled here - they have special treatment below + if (!matchFile) {} + else { + error(fname, FNR, "operator '" a[2] "' must not be preceded by space"); + } +} match(s, / (+|-|\*|\/|==|>=|<=|!=|<<|<<<|>>|&|&&|\|\||\?|:)$/, a) || \ match(s, /(\.|->)$/, a) { if (lenient && fname ~ /(aspen)/ && a[1] != ":") {} # not enabled yet else if (lenient && fname ~ /(fennel|farrago|aspen)/ && a[1] = "+") {} # not enabled yet else if (a[1] == ":" && s ~ /(case.*|default):$/) { # ignore e.g. "case 5:" - } else if ((a[1] == "*" || a[1] == "&") && isCpp(fname) && s ~ /^[[:alnum:]:_ ]* [*&]$/) { + } else if ((a[1] == "*" || a[1] == "&") && isCpp && s ~ /^[[:alnum:]:_ ]* [*&]$/) { # ignore e.g. "const int *\nClass::Subclass2::method(int x)" } else { error(fname, FNR, "operator '" a[1] "' must not be at end of line"); } } +match(s, /^ *(=) /, a) { + error(fname, FNR, "operator '" a[1] "' must not be at start of line"); +} +match(s, /([[:alnum:]~]+)( )([(])/, a) { + # (, < and > are not handled here - they have special treatment below + if (!matchFile) {} + else if (isJava && a[1] ~ /\<(if|while|for|catch|switch|case|return|throw|synchronized|assert)\>/) {} + else if (isCpp && a[1] ~ /\<(if|while|for|catch|switch|case|return|throw|operator|void|PBuffer)\>/) {} + else if (isCpp && s ~ /^#define /) {} + else { + error(fname, FNR, "there must be no space before '" a[3] "' in fun call or fun decl"); + } +} s ~ /\<[[:digit:][:lower:]][[:alnum:]_]*' could be a template + if (!matchFile) {} + else if (isCpp) {} # in C++ 'xyz<5>' could be a template else { error(fname, FNR, "operator '<' must be preceded by space"); } } s ~ /\<[[:digit:][:lower:]][[:alnum:]_]*>/ { # E.g. "g>" but not "String>" as in "List" - if (!matchFile(fname)) {} - else if (isCpp(fname)) {} # in C++ 'xyz' could be a template + if (!matchFile) {} + else if (isCpp) {} # in C++ 'xyz' could be a template else { error(fname, FNR, "operator '>' must be preceded by space"); } } match(s, /<([[:digit:][:lower:]][[:alnum:].]*)\>/, a) { - if (!matchFile(fname)) {} - else if (isCpp(fname)) { + if (!matchFile) {} + else if (isCpp) { # in C++, template and include generate too many false positives - } else if (isJava(fname) && a[1] ~ /(int|char|long|boolean|byte|double|float)/) { + } else if (isJava && a[1] ~ /(int|char|long|boolean|byte|double|float)/) { # Allow e.g. 'List' - } else if (isJava(fname) && a[1] ~ /^[[:lower:]]+\./) { + } else if (isJava && a[1] ~ /^[[:lower:]]+\./) { # Allow e.g. 'List' } else { error(fname, FNR, "operator '<' must be followed by space"); } } match(s, /^(.*[^-])>([[:digit:][:lower:]][[:alnum:]]*)\>/, a) { - if (!matchFile(fname)) {} - else if (isJava(fname) && a[1] ~ /.*\.<.*/) { + if (!matchFile) {} + else if (isJava && a[1] ~ /.*\.<.*/) { # Ignore 'Collections.member' } else { error(fname, FNR, "operator '>' must be followed by space"); } } s ~ /[[(] / { - if (!matchFile(fname)) {} + if (!matchFile) {} else if (s ~ /[[(] +\\$/) {} # ignore '#define foo( \' else { error(fname, FNR, "( or [ must not be followed by space"); } } s ~ / [])]/ { - if (!matchFile(fname)) {} + if (!matchFile) {} else if (s ~ /^ *\)/ && previousLineEndedInCloseBrace) {} # ignore "bar(new Foo() { } );" else { error(fname, FNR, ") or ] must not be followed by space"); } } s ~ /}/ { - if (!matchFile(fname)) {} + if (!matchFile) {} else if (s !~ /}( |;|,|$|\))/) { error(fname, FNR, "} must be followed by space"); } else if (s !~ /( )*}/) { @@ -379,7 +443,7 @@ s ~ /}/ { } } s ~ /{/ { - if (!matchFile(fname)) {} + if (!matchFile) {} else if (s ~ /(\]\)?|=) *{/) {} # ignore e.g. "(int[]) {1, 2}" or "int[] x = {1, 2}" else if (s ~ /\({/) {} # ignore e.g. @SuppressWarnings({"unchecked"}) else if (s ~ /{ *(\/\/|\/\*)/) {} # ignore e.g. "do { // a comment" @@ -391,7 +455,7 @@ s ~ /{/ { else if (s ~ /\\$/) {} # ignore multiline macros else if (s ~ /{}/) { # e.g. "Constructor(){}" error(fname, FNR, "{} must be preceded by space and at end of line"); - } else if (isCpp(fname) && s ~ /{ *\\$/) { + } else if (isCpp && s ~ /{ *\\$/) { # ignore - "{" can be followed by "\" in c macro } else if (s !~ /{$/) { error(fname, FNR, "{ must be at end of line"); @@ -409,8 +473,8 @@ s ~ /{/ { } } s ~ /(^| )(class|interface|enum) / || -s ~ /(^| )namespace / && isCpp(fname) { - if (isCpp(fname) && s ~ /;$/) {} # ignore type declaration +s ~ /(^| )namespace / && isCpp { + if (isCpp && s ~ /;$/) {} # ignore type declaration else { classDeclStartLine = FNR; t = s; @@ -494,7 +558,7 @@ END { gsub(".*/", "", basename); gsub(lf, "", lastNonEmptyLine); terminator = "// End " basename; - if (matchFile(fname) && (lastNonEmptyLine != terminator)) { + if (matchFile && (lastNonEmptyLine != terminator)) { error(fname, FNR, sprintf("Last line should be %c%s%c", 39, terminator, 39)); } } diff --git a/checkFile.sh b/checkFile.sh old mode 100644 new mode 100755 index 6050c6e..bc4b0e4 --- a/checkFile.sh +++ b/checkFile.sh @@ -1,5 +1,5 @@ #!/bin/bash -# $Id: //open/util/bin/checkFile#23 $ +# $Id: //open/mondrian-release/3.2/bin/checkFile.sh#2 $ # Checks that a file is valid. # Used by perforce submit trigger, via runTrigger. # The file is deemed to be valid if this command produces no output. @@ -25,6 +25,8 @@ usage() { echo "checkFile [ ] --opened" echo " Checks all files that are opened for edit in the current" echo " perforce client." + echo "checkFile [ ] --under " + echo " Recursively checks all files under a given directory." echo "checkFile --help" echo " Prints this help." echo @@ -86,6 +88,7 @@ doCheck() { */farrago/src/net/sf/farrago/test/FarragoSqlTestWrapper.java | \ */farrago/src/org/eigenbase/jdbc4/*.java | \ */farrago/src/org/eigenbase/lurql/parser/*.java | \ + */farrago/src/com/lucidera/lurql/parser/*.java | \ */farrago/src/org/eigenbase/resource/EigenbaseResource*.java | \ */farrago/src/org/eigenbase/sql/parser/impl/*.java) return @@ -93,6 +96,11 @@ doCheck() { # Exceptions for fennel */fennel/CMakeFiles/CompilerIdCXX/CMakeCXXCompilerId.cpp | \ + */fennel/disruptivetech/calc/CalcGrammar.tab.cpp | \ + */fennel/disruptivetech/calc/CalcGrammar.cpp | \ + */fennel/disruptivetech/calc/CalcGrammar.h | \ + */fennel/disruptivetech/calc/CalcLexer.cpp | \ + */fennel/disruptivetech/calc/CalcLexer.h | \ */fennel/calculator/CalcGrammar.tab.cpp | \ */fennel/calculator/CalcGrammar.cpp | \ */fennel/calculator/CalcGrammar.h | \ @@ -118,6 +126,7 @@ doCheck() { # Only validate .java and .cup files at present. *.java|*.cup|*.h|*.cpp) ;; + *) return ;; @@ -126,25 +135,7 @@ doCheck() { # Set maxLineLength if it is not already set. ('checkFile --opened' # sets it to the strictest value, 80). if [ ! "$maxLineLength" ]; then - case "$filePath" in - */aspen/*) - if [ "$strict" ]; then - maxLineLength=80 - else - maxLineLength=95 - fi - ;; - */mondrian/*) - if [ "$strict" ]; then - maxLineLength=80 - else - maxLineLength=90 - fi - ;; - *) - maxLineLength=80 - ;; - esac + maxLineLength=80 fi # Check whether there are tabs, or lines end with spaces @@ -153,17 +144,30 @@ doCheck() { # todo: check that every class has javadoc # todo: check that every top-level class has @author and @version # todo: check c++ files - if [ ! -f "$CHECKFILE_AWK" ] - then - CHECKFILE_AWK="$(dirname $(readlink -f $0))/checkFile.awk" + if test "$deferred" ; then + echo "$file" >> "${deferred_file}" + else + gawk -f "$CHECKFILE_AWK" \ + -v fname="$filePath" \ + -v lenient="$lenient" \ + -v maxLineLength="$maxLineLength" \ + "$file" fi - cat "$file" | - gawk -f "$CHECKFILE_AWK" \ - -v fname="$filePath" \ - -v lenient="$lenient" \ - -v maxLineLength="$maxLineLength" } +doCheckDeferred() { + if [ -s "${deferred_file}" ]; then + maxLineLength=80 + cat "${deferred_file}" | + xargs gawk -f "$CHECKFILE_AWK" \ + -v lenient="$lenient" \ + -v maxLineLength="$maxLineLength" + fi + rm -f "${deferred_file}" +} + +export deferred=true + # 'test' is an undocumented flag, overriding the default behavior which is # to ignore our own test files test= @@ -192,18 +196,57 @@ fi depotPath= if [ "$1" == --depotPath ]; then depotPath="$2" + deferred= shift 2 fi opened= if [ "$1" == --opened ]; then opened=true + deferred= shift fi -if [ "$opened" ]; then +under= +if [ "$1" == --under ]; then + if [ "$opened" ]; then + echo "Cannot specify both --under and --opened" + exit 1 + fi + if [ ! -d "$2" ]; then + echo "--under requires a directory; '$2' not found" + exit 1 + fi + under="$2" + shift 2 +fi + +if [ "$1" == --opened ]; then + echo "Cannot specify both --under and --opened" + exit 1 +fi + +if [ ! -f "$CHECKFILE_AWK" ] +then + export CHECKFILE_AWK="$(dirname $(readlink -f $0))/checkFile.awk" +fi + +export deferred_file=/tmp/checkFile_deferred_$$.txt +rm -f "${deferred_file}" + +( +if [ "$under" ]; then + find "$under" -type f | + while read file; do + filePath="$file" + if [ "$depotPath" ]; then + filePath="$depotPath" + fi + doCheck "$filePath" "$file" "" + done +elif [ "$opened" ]; then p4 opened | - gawk -F'#' '{print $1}' | + gawk -F'#' '$2 !~ / - delete/ {print $1}' | while read line; do file=$(p4 where "$line" | gawk '{print $3}' | tr \\\\ /) doCheck "$file" "$file" "80" @@ -218,4 +261,16 @@ else done fi +if test "$deferred"; then + doCheckDeferred +fi +) | tee /tmp/checkFile_output_$$.txt + +status=0 +if [ -s /tmp/checkFile_output_$$.txt ]; then + status=1 +fi + +exit $status + # End checkFile