├── .gitignore ├── README.md └── jsstyle /.gitignore: -------------------------------------------------------------------------------- 1 | /tmp 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jsstyle 2 | 3 | ## Overview 4 | 5 | `jsstyle` is a style checker for JavaScript coding style. This tool is derived 6 | from the cstyle tool used to check for the style used in the Solaris kernel, 7 | sometimes known as "Bill Joy Normal Form". This tool is a *little bit* 8 | configurable. However it strives to enforces a single coding style based on 9 | that cstyle. See "Configuration Options" below. 10 | 11 | The original cstyle tool can be found here: 12 | 13 | 14 | The document describing C Style is available here: 15 | 16 | 17 | Examples of conditions checked by this tool include: 18 | 19 | * Strings must be quoted with single quotes. 20 | * Blocks must be indented with tabs, not spaces. 21 | * Continuation lines must be indented with 4 spaces. 22 | * Keywords (for, if, function, etc.) must be followed with a space. 23 | * One line cannot contain multiple keywords. 24 | * Relational operators must be surrounded with spaces. 25 | * There must be no spaces between tabs, nor tabs between spaces. 26 | * Lines must not end with whitespace. 27 | * Multi-line block comments must start and end with a blank line. 28 | * Return expressions must be parenthesized. 29 | 30 | 31 | ## Status 32 | 33 | No new features planned. The biggest known issue is that jsstyle doesn't grok 34 | regexes, so you usually need to wrap these in JSSTYLED comments (see below). 35 | 36 | 37 | ## Usage 38 | 39 | jsstyle [OPTIONS] file1.js [file2.js ...] 40 | 41 | 42 | ## Configuration Options 43 | 44 | Configuration options may be specified in a file (one option per line) 45 | with the "-f PATH" switch, or on the command line with the "-o 46 | OPTION1,OPTION2" switch. 47 | 48 | As stated about, `jsstyle` is opinionated and intends to stay that way. 49 | That said, this author was arm twisted under duress to allow the following 50 | configurability. 51 | 52 | doxygen Allow doxygen-style block comments `/** /*!`. 53 | splint Allow splint-style lint comments `/*@ ... @*/`. 54 | This is legacy. Does anyone use this? 55 | indent= An integer number of spaces for indentation, or 56 | 'tab' for tab indentation (the default). 57 | strict-indent Boolean option, set to 1 to force indents of spaces 58 | to be a multiple of indent parameter. 59 | line-length An integer number to specify the maximum length 60 | of a line (default: 80) 61 | literal-string-quote 'single' (the default) or 'double'. Specifies 62 | the preferred quote character for literal strings. 63 | unparenthesized-return Boolean option, set to 0 to disable the 64 | "unparenthesized return expression" check. 65 | blank-after-start-comment 66 | Boolean option, set to 0 to disable the 67 | "missing blank after start comment" check. `// ` 68 | blank-after-open-comment 69 | Boolean option, set to 0 to disable the 70 | "missing blank after open comment" check. `/* */` 71 | no-blank-for-anon-function 72 | Boolean option, set to 1 to allow anonymous 73 | functions without blank before paren. `function() { ... }` 74 | continuation-at-front Boolean option, set to 1 to force continations 75 | to be at the beginning rather than end of line. 76 | leading-right-paren-ok Boolean option, set to 1 to allow ) to start a 77 | line. 78 | 79 | whitespace-after-left-paren-ok 80 | Boolean option, allow whitespace after a ( 81 | character. 82 | 83 | leading-comma-ok Boolean option to allow lines to begin with commas 84 | (preceded by whitespace). 85 | 86 | uncuddled-else-ok Boolean option to allow for an else block to begin 87 | on a new line. 88 | 89 | ## "JSSTYLED"-comments 90 | 91 | When you want `jsstyle` to ignore a line, you can use this: 92 | 93 | /* JSSTYLED */ 94 | ignore = this + line; 95 | 96 | Or for a block: 97 | 98 | /* BEGIN JSSTYLED */ 99 | var here 100 | , be 101 | , some = funky 102 | , style 103 | /* END JSSTYLED */ 104 | 105 | 106 | ## License 107 | 108 | CDDL 109 | -------------------------------------------------------------------------------- /jsstyle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | # 3 | # CDDL HEADER START 4 | # 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. 8 | # 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. 13 | # 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] 19 | # 20 | # CDDL HEADER END 21 | # 22 | # 23 | # Copyright 2008 Sun Microsystems, Inc. All rights reserved. 24 | # Use is subject to license terms. 25 | # 26 | # Copyright 2017 Joyent, Inc. All rights reserved. 27 | # 28 | # jsstyle - check for some common stylistic errors. 29 | # 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". 33 | # 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. 36 | # 37 | # A note to the non perl literate: 38 | # 39 | # perl regular expressions are pretty much like egrep 40 | # regular expressions, with the following special symbols 41 | # 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 46 | # \d a digit [0-9] 47 | # \D a non-digit 48 | # \b word boundary (between \w and \W) 49 | # \B non-word boundary 50 | # 51 | 52 | require 5.0; 53 | use IO::File; 54 | use Getopt::Std; 55 | use strict; 56 | 57 | my $usage = 58 | "Usage: jsstyle [-h?vcC] [-t ] [-f ] [-o ] file ... 59 | 60 | Check your JavaScript file for style. 61 | See for details on config options. 62 | Report bugs to . 63 | 64 | Options: 65 | -h print this help and exit 66 | -v verbose 67 | 68 | -c check continuation indentation inside functions 69 | -t specify tab width for line length calculation 70 | -C don't check anything in header block comments 71 | 72 | -f PATH 73 | path to a jsstyle config file 74 | -o OPTION1,OPTION2 75 | set config options, e.g. '-o doxygen,indent=2' 76 | 77 | "; 78 | 79 | my %opts; 80 | 81 | if (!getopts("ch?o:t:f:vC", \%opts)) { 82 | print $usage; 83 | exit 2; 84 | } 85 | 86 | if (defined($opts{'h'}) || defined($opts{'?'})) { 87 | print $usage; 88 | exit; 89 | } 90 | 91 | my $check_continuation = $opts{'c'}; 92 | my $verbose = $opts{'v'}; 93 | my $ignore_hdr_comment = $opts{'C'}; 94 | my $tab_width = $opts{'t'}; 95 | 96 | # By default, tabs are 8 characters wide 97 | if (! defined($opts{'t'})) { 98 | $tab_width = 8; 99 | } 100 | 101 | 102 | # Load config 103 | my %config = ( 104 | indent => "tab", 105 | "line-length" => 80, 106 | doxygen => 0, # doxygen comments: /** ... */ 107 | splint => 0, # splint comments. Needed? 108 | "unparenthesized-return" => 1, 109 | "literal-string-quote" => "single", # 'single' or 'double' 110 | "blank-after-start-comment" => 1, 111 | "blank-after-open-comment" => 1, 112 | "no-blank-for-anon-function" => 0, 113 | "continuation-at-front" => 0, 114 | "leading-right-paren-ok" => 0, 115 | "strict-indent" => 0 116 | ); 117 | sub add_config_var ($$) { 118 | my ($scope, $str) = @_; 119 | 120 | if ($str !~ /^([\w-]+)(?:\s*=\s*(.*?))?$/) { 121 | die "$scope: invalid option: '$str'"; 122 | } 123 | my $name = $1; 124 | my $value = ($2 eq '' ? 1 : $2); 125 | #print "scope: '$scope', str: '$str', name: '$name', value: '$value'\n"; 126 | 127 | # Validate config var. 128 | if ($name eq "indent") { 129 | # A number of spaces or "tab". 130 | if ($value !~ /^\d+$/ && $value ne "tab") { 131 | die "$scope: invalid '$name': must be a number (of ". 132 | "spaces) or 'tab'"; 133 | } 134 | } elsif ($name eq "line-length") { # numeric vars 135 | if ($value !~ /^\d+$/) { 136 | die "$scope: invalid '$name': must be a number"; 137 | } 138 | } elsif ($name eq "doxygen" || # boolean vars 139 | $name eq "splint" || 140 | $name eq "unparenthesized-return" || 141 | $name eq "continuation-at-front" || 142 | $name eq "leading-right-paren-ok" || 143 | $name eq "leading-comma-ok" || 144 | $name eq "uncuddled-else-ok" || 145 | $name eq "whitespace-after-left-paren-ok" || 146 | $name eq "strict-indent" || 147 | $name eq "blank-after-open-comment" || 148 | $name eq "blank-after-start-comment" || 149 | $name eq "no-blank-for-anon-function") { 150 | 151 | if ($value != 1 && $value != 0) { 152 | die "$scope: invalid '$name': don't give a value"; 153 | } 154 | } elsif ($name eq "literal-string-quote") { 155 | if ($value !~ /single|double/) { 156 | die "$scope: invalid '$name': must be 'single' ". 157 | "or 'double'"; 158 | } 159 | } else { 160 | die "$scope: unknown config var: $name"; 161 | } 162 | $config{$name} = $value; 163 | } 164 | 165 | if (defined($opts{'f'})) { 166 | my $path = $opts{'f'}; 167 | my $fh = new IO::File $path, "r"; 168 | if (!defined($fh)) { 169 | die "cannot open config path '$path'"; 170 | } 171 | my $line = 0; 172 | while (<$fh>) { 173 | $line++; 174 | s/^\s*//; # drop leading space 175 | s/\s*$//; # drop trailing space 176 | next if ! $_; # skip empty line 177 | next if /^#/; # skip comments 178 | add_config_var "$path:$line", $_; 179 | } 180 | } 181 | 182 | if (defined($opts{'o'})) { 183 | for my $x (split /,/, $opts{'o'}) { 184 | add_config_var "'-o' option", $x; 185 | } 186 | } 187 | 188 | 189 | my ($filename, $line, $prev); # shared globals 190 | 191 | my $fmt; 192 | my $hdr_comment_start; 193 | 194 | if ($verbose) { 195 | $fmt = "%s: %d: %s\n%s\n"; 196 | } else { 197 | $fmt = "%s: %d: %s\n"; 198 | } 199 | 200 | if ($config{"doxygen"}) { 201 | # doxygen comments look like "/*!" or "/**"; allow them. 202 | $hdr_comment_start = qr/^\s*\/\*[\!\*]?$/; 203 | } else { 204 | $hdr_comment_start = qr/^\s*\/\*$/; 205 | } 206 | 207 | # Note, following must be in single quotes so that \s and \w work right. 208 | my $lint_re = qr/\/\*(?: 209 | jsl:\w+?|ARGSUSED[0-9]*|NOTREACHED|LINTLIBRARY|VARARGS[0-9]*| 210 | CONSTCOND|CONSTANTCOND|CONSTANTCONDITION|EMPTY| 211 | FALLTHRU|FALLTHROUGH|LINTED.*?|PRINTFLIKE[0-9]*| 212 | PROTOLIB[0-9]*|SCANFLIKE[0-9]*|JSSTYLED.*? 213 | )\*\//x; 214 | 215 | my $splint_re = qr/\/\*@.*?@\*\//x; 216 | 217 | my $err_stat = 0; # exit status 218 | 219 | if ($#ARGV >= 0) { 220 | foreach my $arg (@ARGV) { 221 | open(my $fh, "<:encoding(UTF-8)", $arg); 222 | if (!defined($fh)) { 223 | printf "%s: cannot open\n", $arg; 224 | } else { 225 | &jsstyle($arg, $fh); 226 | close $fh; 227 | } 228 | } 229 | } else { 230 | &jsstyle("", *STDIN); 231 | } 232 | exit $err_stat; 233 | 234 | my $no_errs = 0; # set for JSSTYLED-protected lines 235 | 236 | sub err($) { 237 | my ($error) = @_; 238 | unless ($no_errs) { 239 | printf $fmt, $filename, $., $error, $line; 240 | $err_stat = 1; 241 | } 242 | } 243 | 244 | sub err_prefix($$) { 245 | my ($prevline, $error) = @_; 246 | my $out = $prevline."\n".$line; 247 | unless ($no_errs) { 248 | printf $fmt, $filename, $., $error, $out; 249 | $err_stat = 1; 250 | } 251 | } 252 | 253 | sub err_prev($) { 254 | my ($error) = @_; 255 | unless ($no_errs) { 256 | printf $fmt, $filename, $. - 1, $error, $prev; 257 | $err_stat = 1; 258 | } 259 | } 260 | 261 | sub jsstyle($$) { 262 | 263 | my ($fn, $filehandle) = @_; 264 | $filename = $fn; # share it globally 265 | 266 | my $in_cpp = 0; 267 | my $next_in_cpp = 0; 268 | 269 | my $in_comment = 0; 270 | my $in_header_comment = 0; 271 | my $comment_done = 0; 272 | my $in_function = 0; 273 | my $in_function_header = 0; 274 | my $in_declaration = 0; 275 | my $note_level = 0; 276 | my $nextok = 0; 277 | my $nocheck = 0; 278 | 279 | my $in_string = 0; 280 | 281 | my ($okmsg, $comment_prefix); 282 | 283 | $line = ''; 284 | $prev = ''; 285 | reset_indent(); 286 | 287 | line: while (<$filehandle>) { 288 | s/\r?\n$//; # strip return and newline 289 | 290 | # save the original line, then remove all text from within 291 | # double or single quotes, we do not want to check such text. 292 | 293 | $line = $_; 294 | 295 | # 296 | # C allows strings to be continued with a backslash at the end of 297 | # the line. We translate that into a quoted string on the previous 298 | # line followed by an initial quote on the next line. 299 | # 300 | # (we assume that no-one will use backslash-continuation with character 301 | # constants) 302 | # 303 | $_ = '"' . $_ if ($in_string && !$nocheck && !$in_comment); 304 | 305 | # 306 | # normal strings and characters 307 | # 308 | s/'([^\\']|\\.)*'/\'\'/g; 309 | s/"([^\\"]|\\.)*"/\"\"/g; 310 | 311 | # 312 | # Strip out contents of easily identifiable regular expressions so that 313 | # they don't trip us up. We only modify those that start with '^' or end 314 | # with '$' since these are unlikely to be confused with other legitimate 315 | # uses of '/' (such as division or comments). 316 | # 317 | s!/\^([^\\/]|\\.)*/!/ /!g; 318 | s!/([^\\/]|\\.)*\$/!/ /!g; 319 | 320 | # 321 | # detect string continuation 322 | # 323 | if ($nocheck || $in_comment) { 324 | $in_string = 0; 325 | } else { 326 | # 327 | # Now that all full strings are replaced with "", we check 328 | # for unfinished strings continuing onto the next line. 329 | # 330 | $in_string = 331 | (s/([^"](?:"")*)"([^\\"]|\\.)*\\$/$1""/ || 332 | s/^("")*"([^\\"]|\\.)*\\$/""/); 333 | } 334 | 335 | # 336 | # figure out if we are in a cpp directive 337 | # 338 | $in_cpp = $next_in_cpp || /^\s*#/; # continued or started 339 | $next_in_cpp = $in_cpp && /\\$/; # only if continued 340 | 341 | # strip off trailing backslashes, which appear in long macros 342 | s/\s*\\$//; 343 | 344 | # an /* END JSSTYLED */ comment ends a no-check block. 345 | if ($nocheck) { 346 | if (/\/\* *END *JSSTYLED *\*\//) { 347 | $nocheck = 0; 348 | } else { 349 | reset_indent(); 350 | next line; 351 | } 352 | } 353 | 354 | # a /*JSSTYLED*/ comment indicates that the next line is ok. 355 | if ($nextok) { 356 | if ($okmsg) { 357 | err($okmsg); 358 | } 359 | $nextok = 0; 360 | $okmsg = 0; 361 | if (/\/\* *JSSTYLED.*\*\//) { 362 | /^.*\/\* *JSSTYLED *(.*) *\*\/.*$/; 363 | $okmsg = $1; 364 | $nextok = 1; 365 | } 366 | $no_errs = 1; 367 | } elsif ($no_errs) { 368 | $no_errs = 0; 369 | } 370 | 371 | # check length of line. 372 | # first, a quick check to see if there is any chance of being too long. 373 | if ((($line =~ tr/\t/\t/) * ($tab_width - 1)) + length($line) > $config{"line-length"}) { 374 | # yes, there is a chance. 375 | # replace tabs with spaces and check again. 376 | my $eline = $line; 377 | 1 while $eline =~ 378 | s/\t+/' ' x 379 | (length($&) * $tab_width - length($`) % $tab_width)/e; 380 | if (length($eline) > $config{"line-length"}) { 381 | err("line > " . $config{"line-length"} . " characters"); 382 | } 383 | } 384 | 385 | # ignore NOTE(...) annotations (assumes NOTE is on lines by itself). 386 | if ($note_level || /\b_?NOTE\s*\(/) { # if in NOTE or this is NOTE 387 | s/[^()]//g; # eliminate all non-parens 388 | $note_level += s/\(//g - length; # update paren nest level 389 | next; 390 | } 391 | 392 | # a /* BEGIN JSSTYLED */ comment starts a no-check block. 393 | if (/\/\* *BEGIN *JSSTYLED *\*\//) { 394 | $nocheck = 1; 395 | } 396 | 397 | # a /*JSSTYLED*/ comment indicates that the next line is ok. 398 | if (/\/\* *JSSTYLED.*\*\//) { 399 | /^.*\/\* *JSSTYLED *(.*) *\*\/.*$/; 400 | $okmsg = $1; 401 | $nextok = 1; 402 | } 403 | if (/\/\/ *JSSTYLED/) { 404 | /^.*\/\/ *JSSTYLED *(.*)$/; 405 | $okmsg = $1; 406 | $nextok = 1; 407 | } 408 | 409 | # universal checks; apply to everything 410 | if (/\t +\t/) { 411 | err("spaces between tabs"); 412 | } 413 | if (/ \t+ /) { 414 | err("tabs between spaces"); 415 | } 416 | if (/\s$/) { 417 | err("space or tab at end of line"); 418 | } 419 | if (/[^ \t(]\/\*/ && !/\w\(\/\*.*\*\/\);/) { 420 | err("comment preceded by non-blank"); 421 | } 422 | 423 | # is this the beginning or ending of a function? 424 | # (not if "struct foo\n{\n") 425 | if (/^{$/ && $prev =~ /\)\s*(const\s*)?(\/\*.*\*\/\s*)?\\?$/) { 426 | $in_function = 1; 427 | $in_declaration = 1; 428 | $in_function_header = 0; 429 | $prev = $line; 430 | next line; 431 | } 432 | if (/^}\s*(\/\*.*\*\/\s*)*$/) { 433 | if ($prev =~ /^\s*return\s*;/) { 434 | err_prev("unneeded return at end of function"); 435 | } 436 | $in_function = 0; 437 | reset_indent(); # we don't check between functions 438 | $prev = $line; 439 | next line; 440 | } 441 | if (/^\w*\($/) { 442 | $in_function_header = 1; 443 | } 444 | 445 | # a blank line terminates the declarations within a function. 446 | # XXX - but still a problem in sub-blocks. 447 | if ($in_declaration && /^$/) { 448 | $in_declaration = 0; 449 | } 450 | 451 | if ($comment_done) { 452 | $in_comment = 0; 453 | $in_header_comment = 0; 454 | $comment_done = 0; 455 | } 456 | # does this looks like the start of a block comment? 457 | if (/$hdr_comment_start/) { 458 | if ($config{"indent"} eq "tab") { 459 | if (!/^\t*\/\*/) { 460 | err("block comment not indented by tabs"); 461 | } 462 | } elsif (!/^ *\/\*/) { 463 | err("block comment not indented by spaces"); 464 | } 465 | $in_comment = 1; 466 | /^(\s*)\//; 467 | $comment_prefix = $1; 468 | if ($comment_prefix eq "") { 469 | $in_header_comment = 1; 470 | } 471 | $prev = $line; 472 | next line; 473 | } 474 | # are we still in the block comment? 475 | if ($in_comment) { 476 | if (/^$comment_prefix \*\/$/) { 477 | $comment_done = 1; 478 | } elsif (/\*\//) { 479 | $comment_done = 1; 480 | err("improper block comment close") 481 | unless ($ignore_hdr_comment && $in_header_comment); 482 | } elsif (!/^$comment_prefix \*[ \t]/ && 483 | !/^$comment_prefix \*$/) { 484 | err("improper block comment") 485 | unless ($ignore_hdr_comment && $in_header_comment); 486 | } 487 | } 488 | 489 | if ($in_header_comment && $ignore_hdr_comment) { 490 | $prev = $line; 491 | next line; 492 | } 493 | 494 | # check for errors that might occur in comments and in code. 495 | 496 | # allow spaces to be used to draw pictures in header comments. 497 | #if (/[^ ] / && !/".* .*"/ && !$in_header_comment) { 498 | # err("spaces instead of tabs"); 499 | #} 500 | #if (/^ / && !/^ \*[ \t\/]/ && !/^ \*$/ && 501 | # (!/^ \w/ || $in_function != 0)) { 502 | # err("indent by spaces instead of tabs"); 503 | #} 504 | if ($config{"indent"} eq "tab") { 505 | if (/^ {2,}/ && !/^ [^ ]/) { 506 | err("indent by spaces instead of tabs"); 507 | } 508 | } elsif (/^\t/) { 509 | err("indent by tabs instead of spaces") 510 | } elsif (/^( +)/ && !$in_comment) { 511 | my $indent = $1; 512 | if (length($indent) < $config{"indent"}) { 513 | err("indent of " . length($indent) . 514 | " space(s) instead of " . $config{"indent"}); 515 | } elsif ($config{"strict-indent"} && 516 | length($indent) % $config{"indent"} != 0) { 517 | err("indent is " . length($indent) . 518 | " not a multiple of " . $config{'indent'} . " spaces"); 519 | } 520 | } 521 | if (/^\t+ [^ \t\*]/ || /^\t+ \S/ || /^\t+ \S/) { 522 | err("continuation line not indented by 4 spaces"); 523 | } 524 | 525 | # A multi-line block comment must not have content on the first line. 526 | if (/^\s*\/\*./ && !/^\s*\/\*.*\*\// && !/$hdr_comment_start/) { 527 | err("improper first line of block comment"); 528 | } 529 | 530 | if ($in_comment) { # still in comment, don't do further checks 531 | $prev = $line; 532 | next line; 533 | } 534 | 535 | if ($config{"blank-after-open-comment"} && (/[^(]\/\*\S/ || /^\/\*\S/) && 536 | !(/$lint_re/ || ($config{"splint"} && /$splint_re/))) { 537 | err("missing blank after open comment"); 538 | } 539 | if (/\S\*\/[^)]|\S\*\/$/ && 540 | !(/$lint_re/ || ($config{"splint"} && /$splint_re/))) { 541 | err("missing blank before close comment"); 542 | } 543 | if ($config{"blank-after-start-comment"} && /(?\s][!<>=]=/ || /[^<>!=][!<>=]==?[^\s,=]/ || 582 | (/[^->]>[^,=>\s]/ && !/[^->]>$/) || 583 | (/[^<]<[^,=<\s]/ && !/[^<]<$/) || 584 | /[^<\s]<[^<]/ || /[^->\s]>[^>]/) { 585 | err("missing space around relational operator"); 586 | } 587 | if (/\S>>=/ || /\S<<=/ || />>=\S/ || /<<=\S/ || /\S[-+*\/&|^%]=/ || 588 | (/[^-+*\/&|^%!<>=\s]=[^=]/ && !/[^-+*\/&|^%!<>=\s]=$/) || 589 | (/[^!<>=]=[^=\s]/ && !/[^!<>=]=$/)) { 590 | # XXX - should only check this for C++ code 591 | # XXX - there are probably other forms that should be allowed 592 | if (!/\soperator=/) { 593 | err("missing space around assignment operator"); 594 | } 595 | } 596 | if (/[,;]\S/ && !/\bfor \(;;\)/ && 597 | # Allow a comma in a regex quantifier. 598 | !/\/.*?\{\d+,?\d*\}.*?\//) { 599 | err("comma or semicolon followed by non-blank"); 600 | } 601 | # check for commas preceded by blanks 602 | if ((!$config{"leading-comma-ok"} && /^\s*,/) || (!/^\s*,/ && /\s,/)) { 603 | err("comma preceded by blank"); 604 | } 605 | # check for semicolons preceded by blanks 606 | # allow "for" statements to have empty "while" clauses 607 | if (/\s;/ && !/^[\t]+;$/ && !/^\s*for \([^;]*; ;[^;]*\)/) { 608 | err("semicolon preceded by blank"); 609 | } 610 | if (!$config{"continuation-at-front"} && /^\s*(&&|\|\|)/) { 611 | err("improper boolean continuation"); 612 | } elsif ($config{"continuation-at-front"} && /(&&|\|\||[^+]\+)$/) { 613 | err("improper continuation"); 614 | } 615 | if (/\S *(&&|\|\|)/ || /(&&|\|\|) *\S/) { 616 | err("more than one space around boolean operator"); 617 | } 618 | # We allow methods which look like obj.delete() but not keywords without 619 | # spaces ala: delete(obj) 620 | if (!$config{"no-blank-for-anon-function"} && /(?