5
# The contents of this file are subject to the terms of the
6
# Common Development and Distribution License (the "License").
7
# You may not use this file except in compliance with the License.
9
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10
# or http://www.opensolaris.org/os/licensing.
11
# See the License for the specific language governing permissions
12
# and limitations under the License.
14
# When distributing Covered Code, include this CDDL HEADER in each
15
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16
# If applicable, add the following below this CDDL HEADER, with the
17
# fields enclosed by brackets "[]" replaced with your own identifying
18
# information: Portions Copyright [yyyy] [name of copyright owner]
23
# Copyright 2008 Sun Microsystems, Inc. All rights reserved.
24
# Use is subject to license terms.
26
# Copyright 2011 Joyent, Inc. All rights reserved.
28
# jsstyle - check for some common stylistic errors.
30
# jsstyle is a sort of "lint" for Javascript coding style. This tool is
31
# derived from the cstyle tool, used to check for the style used in the
32
# Solaris kernel, sometimes known as "Bill Joy Normal Form".
34
# There's a lot this can't check for, like proper indentation of code
35
# blocks. There's also a lot more this could check for.
37
# A note to the non perl literate:
39
# perl regular expressions are pretty much like egrep
40
# regular expressions, with the following special symbols
42
# \s any space character
43
# \S any non-space character
44
# \w any "word" character [a-zA-Z0-9_]
45
# \W any non-word character
48
# \b word boundary (between \w and \W)
49
# \B non-word boundary
58
"usage: jsstyle [-chvC] [-o constructs] file ...
59
-c check continuation indentation inside functions
60
-h perform heuristic checks that are sometimes wrong
62
-C don't check anything in header block comments
64
allow a comma-seperated list of optional constructs:
65
doxygen allow doxygen-style block comments (/** /*!)
66
splint allow splint-style lint comments (/*@ ... @*/)
71
if (!getopts("cho:vC", \%opts)) {
76
my $check_continuation = $opts{'c'};
77
my $heuristic = $opts{'h'};
78
my $verbose = $opts{'v'};
79
my $ignore_hdr_comment = $opts{'C'};
81
my $doxygen_comments = 0;
82
my $splint_comments = 0;
84
if (defined($opts{'o'})) {
85
for my $x (split /,/, $opts{'o'}) {
86
if ($x eq "doxygen") {
87
$doxygen_comments = 1;
88
} elsif ($x eq "splint") {
91
print "jsstyle: unrecognized construct \"$x\"\n";
98
my ($filename, $line, $prev); # shared globals
101
my $hdr_comment_start;
104
$fmt = "%s: %d: %s\n%s\n";
106
$fmt = "%s: %d: %s\n";
109
if ($doxygen_comments) {
110
# doxygen comments look like "/*!" or "/**"; allow them.
111
$hdr_comment_start = qr/^\s*\/\*[\!\*]?$/;
113
$hdr_comment_start = qr/^\s*\/\*$/;
116
# Note, following must be in single quotes so that \s and \w work right.
117
my $lint_re = qr/\/\*(?:
118
jsl:\w+?|ARGSUSED[0-9]*|NOTREACHED|LINTLIBRARY|VARARGS[0-9]*|
119
CONSTCOND|CONSTANTCOND|CONSTANTCONDITION|EMPTY|
120
FALLTHRU|FALLTHROUGH|LINTED.*?|PRINTFLIKE[0-9]*|
121
PROTOLIB[0-9]*|SCANFLIKE[0-9]*|JSSTYLED.*?
124
my $splint_re = qr/\/\*@.*?@\*\//x;
126
my $err_stat = 0; # exit status
129
foreach my $arg (@ARGV) {
130
my $fh = new IO::File $arg, "r";
132
printf "%s: cannot open\n", $arg;
139
&jsstyle("<stdin>", *STDIN);
143
my $no_errs = 0; # set for JSSTYLED-protected lines
148
printf $fmt, $filename, $., $error, $line;
154
my ($prevline, $error) = @_;
155
my $out = $prevline."\n".$line;
157
printf $fmt, $filename, $., $error, $out;
165
printf $fmt, $filename, $. - 1, $error, $prev;
172
my ($fn, $filehandle) = @_;
173
$filename = $fn; # share it globally
179
my $in_header_comment = 0;
180
my $comment_done = 0;
182
my $in_function_header = 0;
183
my $in_declaration = 0;
190
my ($okmsg, $comment_prefix);
196
line: while (<$filehandle>) {
197
s/\r?\n$//; # strip return and newline
199
# save the original line, then remove all text from within
200
# double or single quotes, we do not want to check such text.
205
# C allows strings to be continued with a backslash at the end of
206
# the line. We translate that into a quoted string on the previous
207
# line followed by an initial quote on the next line.
209
# (we assume that no-one will use backslash-continuation with character
212
$_ = '"' . $_ if ($in_string && !$nocheck && !$in_comment);
215
# normal strings and characters
217
s/'([^\\']|\\.)*'/\'\'/g;
218
s/"([^\\"]|\\.)*"/\"\"/g;
221
# detect string continuation
223
if ($nocheck || $in_comment) {
227
# Now that all full strings are replaced with "", we check
228
# for unfinished strings continuing onto the next line.
231
(s/([^"](?:"")*)"([^\\"]|\\.)*\\$/$1""/ ||
232
s/^("")*"([^\\"]|\\.)*\\$/""/);
236
# figure out if we are in a cpp directive
238
$in_cpp = $next_in_cpp || /^\s*#/; # continued or started
239
$next_in_cpp = $in_cpp && /\\$/; # only if continued
241
# strip off trailing backslashes, which appear in long macros
244
# an /* END JSSTYLED */ comment ends a no-check block.
246
if (/\/\* *END *JSSTYLED *\*\//) {
254
# a /*JSSTYLED*/ comment indicates that the next line is ok.
261
if (/\/\* *JSSTYLED.*\*\//) {
262
/^.*\/\* *JSSTYLED *(.*) *\*\/.*$/;
271
# check length of line.
272
# first, a quick check to see if there is any chance of being too long.
273
if (($line =~ tr/\t/\t/) * 7 + length($line) > 80) {
274
# yes, there is a chance.
275
# replace tabs with spaces and check again.
278
s/\t+/' ' x (length($&) * 8 - length($`) % 8)/e;
279
if (length($eline) > 80) {
280
err("line > 80 characters");
284
# ignore NOTE(...) annotations (assumes NOTE is on lines by itself).
285
if ($note_level || /\b_?NOTE\s*\(/) { # if in NOTE or this is NOTE
286
s/[^()]//g; # eliminate all non-parens
287
$note_level += s/\(//g - length; # update paren nest level
291
# a /* BEGIN JSSTYLED */ comment starts a no-check block.
292
if (/\/\* *BEGIN *JSSTYLED *\*\//) {
296
# a /*JSSTYLED*/ comment indicates that the next line is ok.
297
if (/\/\* *JSSTYLED.*\*\//) {
298
/^.*\/\* *JSSTYLED *(.*) *\*\/.*$/;
302
if (/\/\/ *JSSTYLED/) {
303
/^.*\/\/ *JSSTYLED *(.*)$/;
308
# universal checks; apply to everything
310
err("spaces between tabs");
313
err("tabs between spaces");
316
err("space or tab at end of line");
318
if (/[^ \t(]\/\*/ && !/\w\(\/\*.*\*\/\);/) {
319
err("comment preceded by non-blank");
322
# is this the beginning or ending of a function?
323
# (not if "struct foo\n{\n")
324
if (/^{$/ && $prev =~ /\)\s*(const\s*)?(\/\*.*\*\/\s*)?\\?$/) {
327
$in_function_header = 0;
331
if (/^}\s*(\/\*.*\*\/\s*)*$/) {
332
if ($prev =~ /^\s*return\s*;/) {
333
err_prev("unneeded return at end of function");
336
reset_indent(); # we don't check between functions
341
$in_function_header = 1;
344
# a blank line terminates the declarations within a function.
345
# XXX - but still a problem in sub-blocks.
346
if ($in_declaration && /^$/) {
352
$in_header_comment = 0;
355
# does this looks like the start of a block comment?
356
if (/$hdr_comment_start/) {
358
err("block comment not indented by tabs");
362
$comment_prefix = $1;
363
if ($comment_prefix eq "") {
364
$in_header_comment = 1;
369
# are we still in the block comment?
371
if (/^$comment_prefix \*\/$/) {
375
err("improper block comment close")
376
unless ($ignore_hdr_comment && $in_header_comment);
377
} elsif (!/^$comment_prefix \*[ \t]/ &&
378
!/^$comment_prefix \*$/) {
379
err("improper block comment")
380
unless ($ignore_hdr_comment && $in_header_comment);
384
if ($in_header_comment && $ignore_hdr_comment) {
389
# check for errors that might occur in comments and in code.
391
# allow spaces to be used to draw pictures in header comments.
392
#if (/[^ ] / && !/".* .*"/ && !$in_header_comment) {
393
# err("spaces instead of tabs");
395
#if (/^ / && !/^ \*[ \t\/]/ && !/^ \*$/ &&
396
# (!/^ \w/ || $in_function != 0)) {
397
# err("indent by spaces instead of tabs");
399
if (/^ {2,}/ && !/^ [^ ]/) {
400
err("indent by spaces instead of tabs");
402
if (/^\t+ [^ \t\*]/ || /^\t+ \S/ || /^\t+ \S/) {
403
err("continuation line not indented by 4 spaces");
406
if (/^\s*\/\*./ && !/^\s*\/\*.*\*\// && !/$hdr_comment_start/) {
407
err("improper first line of block comment");
410
if ($in_comment) { # still in comment, don't do further checks
415
if ((/[^(]\/\*\S/ || /^\/\*\S/) &&
416
!(/$lint_re/ || ($splint_comments && /$splint_re/))) {
417
err("missing blank after open comment");
419
if (/\S\*\/[^)]|\S\*\/$/ &&
420
!(/$lint_re/ || ($splint_comments && /$splint_re/))) {
421
err("missing blank before close comment");
423
if (/\/\/\S/) { # C++ comments
424
err("missing blank after start comment");
426
# check for unterminated single line comments, but allow them when
427
# they are used to comment out the argument list of a function
429
if (/\S.*\/\*/ && !/\S.*\/\*.*\*\// && !/\(\/\*/) {
430
err("unterminated single line comment");
433
if (/^(#else|#endif|#include)(.*)$/) {
439
# delete any comments and check everything else. Note that
440
# ".*?" is a non-greedy match, so that we don't get confused by
441
# multiple comments on the same line.
444
s/\/\/.*$//; # C++ comments
446
# delete any trailing whitespace; we have already checked for that.
449
# following checks do not apply to text in comments.
451
err("literal string using double-quote instead of single");
454
if (/[^=!<>\s][!<>=]=/ || /[^<>!=][!<>=]==?[^\s,=]/ ||
455
(/[^->]>[^,=>\s]/ && !/[^->]>$/) ||
456
(/[^<]<[^,=<\s]/ && !/[^<]<$/) ||
457
/[^<\s]<[^<]/ || /[^->\s]>[^>]/) {
458
err("missing space around relational operator");
460
if (/\S>>=/ || /\S<<=/ || />>=\S/ || /<<=\S/ || /\S[-+*\/&|^%]=/ ||
461
(/[^-+*\/&|^%!<>=\s]=[^=]/ && !/[^-+*\/&|^%!<>=\s]=$/) ||
462
(/[^!<>=]=[^=\s]/ && !/[^!<>=]=$/)) {
463
# XXX - should only check this for C++ code
464
# XXX - there are probably other forms that should be allowed
465
if (!/\soperator=/) {
466
err("missing space around assignment operator");
469
if (/[,;]\S/ && !/\bfor \(;;\)/) {
470
err("comma or semicolon followed by non-blank");
472
# allow "for" statements to have empty "while" clauses
473
if (/\s[,;]/ && !/^[\t]+;$/ && !/^\s*for \([^;]*; ;[^;]*\)/) {
474
err("comma or semicolon preceded by blank");
476
if (/^\s*(&&|\|\|)/) {
477
err("improper boolean continuation");
479
if (/\S *(&&|\|\|)/ || /(&&|\|\|) *\S/) {
480
err("more than one space around boolean operator");
482
if (/\b(delete|typeof|instanceOf|throw|with|catch|new|function|in|for|if|while|switch|return|case)\(/) {
483
err("missing space between keyword and paren");
485
if (/(\b(catch|for|if|with|while|switch|return)\b.*){2,}/) {
486
# multiple "case" and "sizeof" allowed
487
err("more than one keyword on line");
489
if (/\b(delete|typeof|instanceOf|with|throw|catch|new|function|in|for|if|while|switch|return|case)\s\s+\(/ &&
491
err("extra space between keyword and paren");
493
# try to detect "func (x)" but not "if (x)" or
494
# "#define foo (x)" or "int (*func)();"
497
# strip off all keywords on the line
498
s/\b(delete|typeof|instanceOf|throw|with|catch|new|function|in|for|if|while|switch|return|case)\s\(/XXX(/g;
500
s/^#define\s+\w+\s+\(/XXX(/;
501
# do not match things like "void (*f)();"
502
# or "typedef void (func_t)();"
504
s/\b(void)\s+\(+/XXX(/og;
506
err("extra space between function name and left paren");
511
if (/^\s*return\W[^;]*;/ && !/^\s*return\s*\(.*\);/) {
512
err("unparenthesized return expression");
514
if (/\btypeof\b/ && !/\btypeof\s*\(.*\)/) {
515
err("unparenthesized typeof expression");
518
err("whitespace after left paren");
520
# allow "for" statements to have empty "continue" clauses
521
if (/\s\)/ && !/^\s*for \([^;]*;[^;]*; \)/) {
522
err("whitespace before right paren");
524
if (/^\s*\(void\)[^ ]/) {
525
err("missing space after (void) cast");
527
if (/\S{/ && !/({|\(){/) {
528
err("missing space before left brace");
530
if ($in_function && /^\s+{/ &&
531
($prev =~ /\)\s*$/ || $prev =~ /\bstruct\s+\w+$/)) {
532
err("left brace starting a line");
534
if (/}(else|while)/) {
535
err("missing space after right brace");
537
if (/}\s\s+(else|while)/) {
538
err("extra space after right brace");
541
err("preprocessor statement not in column 1");
544
err("blank after preprocessor #");
548
# We completely ignore, for purposes of indentation:
549
# * lines outside of functions
550
# * preprocessor lines
552
if ($check_continuation && $in_function && !$in_cpp) {
557
# cannot check this everywhere due to "struct {\n...\n} foo;"
558
if ($in_function && !$in_declaration &&
559
/}./ && !/}\s+=/ && !/{.*}[;,]$/ && !/}(\s|)*$/ &&
560
!/} (else|while)/ && !/}}/) {
561
err("possible bad text following right brace");
563
# cannot check this because sub-blocks in
564
# the middle of code are ok
565
if ($in_function && /^\s+{/) {
566
err("possible left brace starting a line");
570
if ($prev =~ /^\s*}$/) {
572
"else and right brace should be on same line");
579
err("last line in file is blank");
585
# Continuation-line checking
587
# The rest of this file contains the code for the continuation checking
588
# engine. It's a pretty simple state machine which tracks the expression
589
# depth (unmatched '('s and '['s).
591
# Keep in mind that the argument to process_indent() has already been heavily
592
# processed; all comments have been replaced by control-A, and the contents of
593
# strings and character constants have been elided.
596
my $cont_in; # currently inside of a continuation
597
my $cont_off; # skipping an initializer or definition
598
my $cont_noerr; # suppress cascading errors
599
my $cont_start; # the line being continued
600
my $cont_base; # the base indentation
601
my $cont_first; # this is the first line of a statement
602
my $cont_multiseg; # this continuation has multiple segments
604
my $cont_special; # this is a C statement (if, for, etc.)
605
my $cont_macro; # this is a macro
606
my $cont_case; # this is a multi-line case
608
my @cont_paren; # the stack of unmatched ( and [s we've seen
621
# replace labels with tabs. Note that there may be multiple
626
while (/^(\t*)( *(?:(?:\w+\s*)|(?:case\b[^:]*)): *)(.*)$/) {
627
my ($pre_tabs, $label, $rest) = ($1, $2, $3);
629
while ($label =~ s/^([^\t]*)(\t+)//) {
630
$_ .= "\t" x (length($2) + length($1) / 8);
632
$_ .= ("\t" x (length($label) / 8)).$rest;
642
local $_ = $_[0]; # preserve the global $_
645
s/\s+$//; # Strip trailing whitespace
647
return if (/^$/); # skip empty lines
649
# regexps used below; keywords taking (), macros, and continued cases
650
my $special = '(?:(?:\}\s*)?else\s+)?(?:if|for|while|switch)\b';
651
my $macro = '[A-Z_][A-Z_0-9]*\(';
652
my $case = 'case\b[^:]*$';
654
# skip over enumerations, array definitions, initializers, etc.
655
if ($cont_off <= 0 && !/^\s*$special/ &&
656
(/(?:(?:\b(?:enum|struct|union)\s*[^\{]*)|(?:\s+=\s*)){/ ||
657
(/^\s*{/ && $prev =~ /=\s*(?:\/\*.*\*\/\s*)*$/))) {
659
$cont_off = tr/{/{/ - tr/}/}/;
663
$cont_off += tr/{/{/ - tr/}/}/;
671
err("non-continuation indented 4 spaces");
672
$cont_noerr = 1; # stop reporting
674
$_ = delabel($_); # replace labels with tabs
676
# check if the statement is complete
677
return if (/^\s*\}?$/);
678
return if (/^\s*\}?\s*else\s*\{?$/);
679
return if (/^\s*do\s*\{?$/);
681
return if (/}[,;]?$/);
683
# Allow macros on their own lines
684
return if (/^\s*[A-Z_][A-Z_0-9]*$/);
686
# cases we don't deal with, generally non-kosher
688
err("stuff after {");
692
# Get the base line, and set up the state machine
700
# certain things need special processing
701
$cont_special = /^\s*$special/? 1 : 0;
702
$cont_macro = /^\s*$macro/? 1 : 0;
703
$cont_case = /^\s*$case/? 1 : 0;
707
# Strings may be pulled back to an earlier (half-)tabstop
708
unless ($cont_noerr || /^$cont_base / ||
709
(/^\t*(?: )?(?:gettext\()?\"/ && !/^$cont_base\t/)) {
710
err_prefix($cont_start,
711
"continuation should be indented 4 spaces");
715
my $rest = $_; # keeps the remainder of the line
718
# The split matches 0 characters, so that each 'special' character
719
# is processed separately. Parens and brackets are pushed and
720
# popped off the @cont_paren stack. For normal processing, we wait
721
# until a ; or { terminates the statement. "special" processing
722
# (if/for/while/switch) is allowed to stop when the stack empties,
723
# as is macro processing. Case statements are terminated with a :
724
# and an empty paren stack.
726
foreach $_ (split /[^\(\)\[\]\{\}\;\:]*/) {
727
next if (length($_) == 0);
729
# rest contains the remainder of the line
730
my $rxp = "[^\Q$_\E]*\Q$_\E";
734
push @cont_paren, $_;
735
} elsif (/\)/ || /\]/) {
739
my $old = (pop @cont_paren);
740
if (!defined($old)) {
741
err("unexpected '$cur'");
744
} elsif ($old ne $_) {
745
err("'$cur' mismatched with '$old'");
751
# If the stack is now empty, do special processing
752
# for if/for/while/switch and macro statements.
754
next if (@cont_paren != 0);
756
if ($rest =~ /^\s*{?$/) {
760
if ($rest =~ /^\s*;$/) {
761
err("empty if/for/while body ".
762
"not on its own line");
766
if (!$cont_first && $cont_multiseg == 1) {
767
err_prefix($cont_start,
768
"multiple statements continued ".
769
"over multiple lines");
771
} elsif ($cont_multiseg == 0) {
774
# We've finished this section, start
775
# processing the next.
787
} elsif (!$cont_special) {
788
err("unexpected ;") if (@cont_paren != 0);
789
if (!$cont_first && $cont_multiseg == 1) {
790
err_prefix($cont_start,
791
"multiple statements continued ".
792
"over multiple lines");
794
} elsif ($cont_multiseg == 0) {
801
if ($rest =~ /^\s*special/) {
802
err("if/for/while/switch not started ".
808
err("{ while in parens/brackets") if (@cont_paren != 0);
809
err("stuff after {") if ($rest =~ /[^\s}]/);
813
err("} while in parens/brackets") if (@cont_paren != 0);
814
if (!$cont_special && $rest !~ /^\s*(while|else)\b/) {
818
err("stuff after }");
823
} elsif (/\:/ && $cont_case && @cont_paren == 0) {
824
err("stuff after multi-line case") if ($rest !~ /$^/);
830
# End of a statement or if/while/for loop. Reset
831
# cont_special and cont_macro based on the rest of the
833
$cont_special = ($rest =~ /^\s*$special/)? 1 : 0;
834
$cont_macro = ($rest =~ /^\s*$macro/)? 1 : 0;
838
$cont_noerr = 0 if (!$cont_in);