#!/usr/bin/perl -w # # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2015 Toomas Soome <tsoome@me.com> # Copyright 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # jstyle - check for some common stylistic errors. # require 5.006; use Getopt::Std; use strict; my $usage = "usage: jstyle [-c] [-h] [-p] [-t] [-v] [-C] file ... -c check continuation line indenting -h perform heuristic checks that are sometimes wrong -p perform some of the more picky checks -t insist on indenting by tabs -v verbose -C don't check anything in header block comments "; my %opts; # Keep -s, as it's been around for a while. It just doesn't do anything # anymore. if (!getopts("chpstvC", \%opts)) { print $usage; exit 2; } my $check_continuation = $opts{'c'}; my $heuristic = $opts{'h'}; my $picky = $opts{'p'}; my $tabs = $opts{'t'}; my $verbose = $opts{'v'}; my $ignore_hdr_comment = $opts{'C'}; my ($filename, $line, $prev); my $err_stat = 0; # Exit status my $fmt; if ($verbose) { $fmt = "%s: %d: %s\n%s\n"; } else { $fmt = "%s: %d: %s\n"; } # Note, following must be in single quotes so that \s and \w work right. my $typename = '(int|char|boolean|byte|short|long|float|double)'; my $keywords = '(for|if|while|switch|return|catch|synchronized|throw|assert)'; # See perlre(1) for the meaning of (??{ ... }) my $annotations = ""; $annotations = qr/@\w+\((?:(?>[^()]+)|(??{ $annotations }))*\)/; my $generics = ""; $generics = qr/<(([\s\w,.?[\]]| & )+|(??{ $generics }))*>/; my $relationalops = qr/>=|<=|<|>|!=|==/; my $shiftops = qr/<<<|>>>|<<|>>/; my $shiftassignmentops = qr/[<>]{2,3}=/; my $assignmentops = qr/[-+\/*|&^%]?=/; # These need to be in decreasing order of length my $allops = qr/$shiftassignmentops|$shiftops|$relationalops|$assignmentops/; if ($#ARGV >= 0) { foreach my $arg (@ARGV) { if (!open(STDIN, $arg)) { printf "%s: can not open\n", $arg; } else { &jstyle($arg); close STDIN; } } } else { &jstyle("<stdin>"); } exit $err_stat; sub err($) { if ($verbose) { printf $fmt, $filename, $., $_[0], $line; } else { printf $fmt, $filename, $., $_[0]; } $err_stat = 1; } sub jstyle($) { my $in_comment = 0; my $in_header_comment = 0; my $in_continuation = 0; my $in_class = 0; my $in_declaration = 0; my $nextok = 0; my $nocheck = 0; my $expect_continuation = 0; my $continuation_indent; my $okmsg; my $comment_prefix; my $comment_done; my $cpp_comment; $filename = $_[0]; line: while (<STDIN>) { s/\r?\n$//; # strip return and newline # save the original line, then remove all text from within # double or single quotes, we do not want to check such text. $line = $_; s/"[^"]*"/\"\"/g; s/'.'/''/g; # an /* END JSTYLED */ comment ends a no-check block. if ($nocheck) { if (/\/\* *END *JSTYLED *\*\//) { $nocheck = 0; } else { next line; } } # a /*JSTYLED*/ comment indicates that the next line is ok. if ($nextok) { if ($okmsg) { err($okmsg); } $nextok = 0; $okmsg = 0; if (/\/\* *JSTYLED.*\*\//) { /^.*\/\* *JSTYLED *(.*) *\*\/.*$/; $okmsg = $1; $nextok = 1; } $prev = $line; next line; } # remember whether we expect to be inside a continuation line. $in_continuation = $expect_continuation; # check for proper continuation line. blank lines # in the middle of the # continuation do not count. # XXX - only check within functions. if ($check_continuation && $expect_continuation && $in_class && !/^\s*$/) { # continuation line must start with whitespace of # previous line, plus either 4 spaces or a tab, but # do not check lines that start with a string constant # since they are often shifted to the left to make them # fit on the line. if (!/^$continuation_indent \S/ && !/^$continuation_indent\t\S/ && !/^\s*"/) { err("continuation line improperly indented"); } $expect_continuation = 0; } # a /* BEGIN JSTYLED */ comment starts a no-check block. if (/\/\* *BEGIN *JSTYLED *\*\//) { $nocheck = 1; } # a /*JSTYLED*/ comment indicates that the next line is ok. if (/\/\* *JSTYLED.*\*\//) { /^.*\/\* *JSTYLED *(.*) *\*\/.*$/; $okmsg = $1; $nextok = 1; } if (/\/\/ *JSTYLED/) { /^.*\/\/ *JSTYLED *(.*)$/; $okmsg = $1; $nextok = 1; } # is this the beginning or ending of a class? if (/^(public\s+)*\w(class|interface)\s/) { $in_class = 1; $in_declaration = 1; $prev = $line; next line; } if (/^}\s*(\/\*.*\*\/\s*)*$/) { $in_class = 0; $prev = $line; next line; } if ($comment_done) { $in_comment = 0; $in_header_comment = 0; $comment_done = 0; } # does this looks like the start of a block comment? if (/^\s*\/\*/ && !/^\s*\/\*.*\*\//) { if (/^\s*\/\*./ && !/^\s*\/\*\*$/) { err("improper first line of block comment"); } if (!/^(\t| )*\/\*/) { err("block comment not indented properly"); } $in_comment = 1; /^(\s*)\//; $comment_prefix = $1; if ($comment_prefix eq "") { $in_header_comment = 1; } $prev = $line; next line; } # are we still in the block comment? if ($in_comment) { if (/^$comment_prefix \*\/$/) { $comment_done = 1; } elsif (/\*\//) { $comment_done = 1; err("improper block comment close") unless ($ignore_hdr_comment && $in_header_comment); } elsif (!/^$comment_prefix \*[ \t]/ && !/^$comment_prefix \*$/) { err("improper block comment") unless ($ignore_hdr_comment && $in_header_comment); } } if ($in_header_comment && $ignore_hdr_comment) { $prev = $line; next line; } # check for errors that might occur in comments and in code. # check length of line. # first, a quick check to see if there is any chance of being too long. if ($line =~ tr/\t/\t/ * 7 + length($line) > 80) { # yes, there is a chance. # replace tabs with spaces and check again. my $eline = $line; 1 while $eline =~ s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e; if (length($eline) > 80) { err("line > 80 characters"); } } # Allow spaces to be used to draw pictures in header comments, but # disallow blocks of spaces almost everywhere else. In particular, # five spaces are also allowed at the end of a line's indentation # if the rest of the line belongs to a block comment. if (!$in_header_comment && /[^ ] / && !(/^\t* \*/ && !/^\t* \*.* /)) { err("spaces instead of tabs"); } if ($tabs && /^ / && !/^ \*[ \t\/]/ && !/^ \*$/ && (!/^ \w/ || $in_class != 0)) { err("indent by spaces instead of tabs"); } if (!$in_comment && (/^(\t )* {1,3}\S/ || /^(\t )* {5,7}\S/) && !(/^\s*[-+|&\/?:=]/ || ($prev =~ /,\s*$/))) { err("indent not a multiple of 4"); } if (/\s$/) { err("space or tab at end of line"); } if (0) { if (/^[\t]+ [^ \t\*]/ || /^[\t]+ \S/ || /^[\t]+ \S/) { err("continuation line not indented by 4 spaces"); } } if (/\/\//) { $cpp_comment = 1; } if (!$cpp_comment && /[^ \t(\/]\/\*/ && !/\w\(\/\*.*\*\/\);/) { err("comment preceded by non-blank"); } if (/\t +\t/) { err("spaces between tabs"); } if (/ \t+ /) { err("tabs between spaces"); } if ($in_comment) { # still in comment $prev = $line; next line; } if (!$cpp_comment && ((/\/\*\S/ && !/\/\*\*/) || /\/\*\*\S/)) { err("missing blank after open comment"); } if (!$cpp_comment && /\S\*\//) { err("missing blank before close comment"); } # check for unterminated single line comments. if (/\S.*\/\*/ && !/\S.*\/\*.*\*\//) { err("unterminated single line comment"); } # delete any comments and check everything else. Be sure to leave # //-style comments intact, and if there are multiple comments on a # line, preserve whatever's in between. s/(?<!\/)\/\*.*?\*\///g; # Check for //-style comments only outside of block comments if (m{(//(?!$))} && substr($_, $+[0], 1) !~ /[ \t]/) { err("missing blank after start comment"); } s/\/\/.*$//; # C++ comments $cpp_comment = 0; # delete any trailing whitespace; we have already checked for that. s/\s*$//; # We don't style (yet) what's inside annotations, so just delete them. s/$annotations//; # following checks do not apply to text in comments. # if it looks like an operator at the end of the line, and it is # not really the end of a comment (...*/), and it is not really # a label (done:), and it is not a case label (case FOO:), # or we are not in a function definition (ANSI C style) and the # operator is a "," (to avoid hitting "int\nfoo(\n\tint i,\n\tint j)"), # or we are in a function and the operator is a # "*" (to avoid hitting on "char*\nfunc()"). if ((/[-+|&\/?:=]$/ && !/\*\/$/ && !/^\s*\w*:$/ && !/^\s\s*case\s\s*\w*:$/) || /,$/ || ($in_class && /\*$/)) { $expect_continuation = 1; if (!$in_continuation) { /^(\s*)\S/; $continuation_indent = $1; } } while (/($allops)/g) { my $z = substr($_, $-[1] - 1); if ($z !~ /\s\Q$1\E(?:\s|$)/) { my $m = $1; my $shift; # @+ is available only in the currently active # dynamic scope. Assign it to a new variable # to pass it into the if block. if ($z =~ /($generics)/ && ($shift = $+[1])) { pos $_ += $shift; next; } # These need to be in decreasing order of length # (violable as long as there's no ambiguity) my $nospace = "missing space around"; if ($m =~ $shiftassignmentops) { err("$nospace assignment operator"); } elsif ($m =~ $shiftops) { err("$nospace shift operator"); } elsif ($m =~ $relationalops) { err("$nospace relational operator"); } elsif ($m =~ $assignmentops) { err("$nospace assignment operator"); } } } if (/[,;]\S/ && !/\bfor \(;;\)/) { err("comma or semicolon followed by non-blank"); } # allow "for" statements to have empty "while" clauses if (/\s[,;]/ && !/^[\t]+;$/ && !/^\s*for \([^;]*; ;[^;]*\)/) { err("comma or semicolon preceded by blank"); } if (0) { if (/^\s*(&&|\|\|)/) { err("improper boolean continuation"); } } if ($picky && /\S *(&&|\|\|)/ || /(&&|\|\|) *\S/) { err("more than one space around boolean operator"); } if (/\b$keywords\(/) { err("missing space between keyword and paren"); } if (/(\b$keywords\b.*){2,}/ && !/\bcase\b.*/) { # "case" excepted err("more than one keyword on line"); } if (/\b$keywords\s\s+\(/ && !/^#if\s+\(/) { err("extra space between keyword and paren"); } # try to detect "func (x)" but not "if (x)" or # "int (*func)();" if (/\w\s\(/) { my $save = $_; # strip off all keywords on the line s/\b$keywords\s\(/XXX(/g; #s/\b($typename|void)\s+\(+/XXX(/og; if (/\w\s\(/) { err("extra space between function name and left paren"); } $_ = $save; } if (/\(\s/) { err("whitespace after left paren"); } # allow "for" statements to have empty "continue" clauses if (/\s\)/ && !/^\s*for \([^;]*;[^;]*; \)/) { err("whitespace before right paren"); } if (/^\s*\(void\)[^ ]/) { err("missing space after (void) cast"); } if (/\S\{/ && !/\{\{/) { err("missing space before left brace"); } if ($in_class && /^\s+{/ && ($prev =~ /\)\s*$/)) { err("left brace starting a line"); } if (/}(else|while)/) { err("missing space after right brace"); } if (/}\s\s+(else|while)/) { err("extra space after right brace"); } if (/\b$typename\*/o) { err("missing space between type name and *"); } if ($heuristic) { # cannot check this everywhere due to "struct {\n...\n} foo;" if ($in_class && !$in_declaration && /}./ && !/}\s+=/ && !/{.*}[;,]$/ && !/}(\s|)*$/ && !/} (else|while)/ && !/}}/) { err("possible bad text following right brace"); } # cannot check this because sub-blocks in # the middle of code are ok if ($in_class && /^\s+{/) { err("possible left brace starting a line"); } } if (/^\s*else\W/) { if ($prev =~ /^\s*}$/) { my $str = "else and right brace should be on same line"; printf $fmt, $filename, $., $str, $prev; if ($verbose) { printf "%s\n", $line; } } } $prev = $line; } if ($picky && $prev eq "") { err("last line in file is blank"); } }