From fdc1028cfd9e6563dbbe950cdd539559ce8d6353 Mon Sep 17 00:00:00 2001 From: Alexander Sulfrian Date: Wed, 25 Apr 2012 00:13:37 +0200 Subject: emacs: added some additional modes --- emacs.d/lisp/arduino-mode.el | 147 + emacs.d/lisp/filladapt.el | 981 ++++ emacs.d/lisp/jshint-mode/LICENSE | 23 + emacs.d/lisp/jshint-mode/README.markdown | 38 + emacs.d/lisp/jshint-mode/example.png | Bin 0 -> 37639 bytes emacs.d/lisp/jshint-mode/flymake-jshint.el | 90 + emacs.d/lisp/jshint-mode/jshint-emacs.js | 8 + emacs.d/lisp/jshint-mode/jshint-mode.js | 73 + emacs.d/lisp/jshint-mode/jshint.js | 5921 ++++++++++++++++++++ emacs.d/lisp/jshint-mode/jslint.js | 5701 +++++++++++++++++++ .../jshint-mode/node_modules/formidable/.gitignore | 4 + .../jshint-mode/node_modules/formidable/Makefile | 9 + .../jshint-mode/node_modules/formidable/Readme.md | 258 + .../lisp/jshint-mode/node_modules/formidable/TODO | 3 + .../formidable/benchmark/bench-multipart-parser.js | 70 + .../node_modules/formidable/example/post.js | 43 + .../node_modules/formidable/example/upload.js | 48 + .../jshint-mode/node_modules/formidable/index.js | 1 + .../node_modules/formidable/lib/file.js | 61 + .../node_modules/formidable/lib/incoming_form.js | 349 ++ .../node_modules/formidable/lib/index.js | 3 + .../formidable/lib/multipart_parser.js | 303 + .../formidable/lib/querystring_parser.js | 25 + .../node_modules/formidable/lib/util.js | 6 + .../node_modules/formidable/package.json | 15 + .../node_modules/formidable/test/common.js | 24 + .../formidable/test/fixture/multipart.js | 72 + .../test/integration/test-multipart-parser.js | 80 + .../formidable/test/simple/test-file.js | 104 + .../formidable/test/simple/test-incoming-form.js | 710 +++ .../test/simple/test-multipart-parser.js | 50 + .../test/simple/test-querystring-parser.js | 45 + .../test/system/test-multi-video-upload.js | 72 + emacs.d/lisp/jshint-mode/package.json | 15 + emacs.d/lisp/promela-mode.el | 985 ++++ 35 files changed, 16337 insertions(+) create mode 100644 emacs.d/lisp/arduino-mode.el create mode 100644 emacs.d/lisp/filladapt.el create mode 100644 emacs.d/lisp/jshint-mode/LICENSE create mode 100644 emacs.d/lisp/jshint-mode/README.markdown create mode 100644 emacs.d/lisp/jshint-mode/example.png create mode 100644 emacs.d/lisp/jshint-mode/flymake-jshint.el create mode 100755 emacs.d/lisp/jshint-mode/jshint-emacs.js create mode 100644 emacs.d/lisp/jshint-mode/jshint-mode.js create mode 100644 emacs.d/lisp/jshint-mode/jshint.js create mode 100644 emacs.d/lisp/jshint-mode/jslint.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/.gitignore create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/Makefile create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/Readme.md create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/TODO create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/benchmark/bench-multipart-parser.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/example/post.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/example/upload.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/index.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/lib/file.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/lib/incoming_form.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/lib/index.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/lib/multipart_parser.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/lib/querystring_parser.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/lib/util.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/package.json create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/test/common.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/test/fixture/multipart.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/test/integration/test-multipart-parser.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/test/simple/test-file.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/test/simple/test-incoming-form.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/test/simple/test-multipart-parser.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/test/simple/test-querystring-parser.js create mode 100644 emacs.d/lisp/jshint-mode/node_modules/formidable/test/system/test-multi-video-upload.js create mode 100644 emacs.d/lisp/jshint-mode/package.json create mode 100644 emacs.d/lisp/promela-mode.el (limited to 'emacs.d/lisp') diff --git a/emacs.d/lisp/arduino-mode.el b/emacs.d/lisp/arduino-mode.el new file mode 100644 index 0000000..0f434b5 --- /dev/null +++ b/emacs.d/lisp/arduino-mode.el @@ -0,0 +1,147 @@ +;;; arduino-mode.el --- Major mode for the Arduino language + +;; Copyright (C) 2008 Christopher Grim + +;; Author: Christopher Grim +;; Keywords: languages, arduino + +;; This file is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation; either version 3, or (at your option) +;; any later version. + +;; This file is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with GNU Emacs; see the file COPYING. If not, write to +;; the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: +;; +;; Based on derived-mode-ex.el found here: +;; +;; . +;; + +;;; Code: +(require 'cc-mode) + +(eval-when-compile + (require 'cc-langs) + (require 'cc-fonts) + (require 'cc-menus)) + +(eval-and-compile + ;; fall back on c-mode + (c-add-language 'arduino-mode 'c-mode)) + +(c-lang-defconst c-primitive-type-kwds + arduino (append '("boolean" "byte") + (c-lang-const c-primitive-type-kwds))) + +(c-lang-defconst c-constant-kwds + arduino (append '("HIGH" "LOW" "INPUT" "OUTPUT") + (c-lang-const c-constant-kwds))) + +(c-lang-defconst c-simple-stmt-kwds + arduino (append '("pinMode" "digitalWrite" "digitalRead" ; Digital I/O + "analogRead" "analogWrite" ; Analog I/O + "shiftOut" "pulseIn" ; Advanced I/O + "millis" "delay" "delayMicroseconds" ; Time + "min" "max" "abs" "constrain" "map" "pow" "sq" "sqrt" "sin" ; Math + "sin" "cos" "tan" ; Trigonometry + "randomSeed" "random" ; Random Numbers + "attachInterrupt" "detachInterrupt" ; External Interrupts + "interrupts" "noInterrupts" ; Interrupts + "begin" "available" "read" "flush" "print" "println") ; Serial Communication + (c-lang-const c-simple-stmt-kwds))) + +(c-lang-defconst c-primary-expr-kwds + arduino (append '("Serial") + (c-lang-const c-primary-expr-kwds))) + +(defgroup arduino nil "Arduino mode customizations") + +(defcustom arduino-font-lock-extra-types nil + "*List of extra types (aside from the type keywords) to recognize in Arduino mode. +Each list item should be a regexp matching a single identifier." :group 'arduino) + +(defconst arduino-font-lock-keywords-1 (c-lang-const c-matchers-1 arduino) + "Minimal highlighting for Arduino mode.") + +(defconst arduino-font-lock-keywords-2 (c-lang-const c-matchers-2 arduino) + "Fast normal highlighting for Arduino mode.") + +(defconst arduino-font-lock-keywords-3 (c-lang-const c-matchers-3 arduino) + "Accurate normal highlighting for Arduino mode.") + +(defvar arduino-font-lock-keywords arduino-font-lock-keywords-3 + "Default expressions to highlight in ARDUINO mode.") + +(defvar arduino-mode-syntax-table nil + "Syntax table used in arduino-mode buffers.") +(or arduino-mode-syntax-table + (setq arduino-mode-syntax-table + (funcall (c-lang-const c-make-mode-syntax-table arduino)))) + +(defvar arduino-mode-abbrev-table nil + "Abbreviation table used in arduino-mode buffers.") + +(c-define-abbrev-table 'arduino-mode-abbrev-table + ;; Keywords that if they occur first on a line might alter the + ;; syntactic context, and which therefore should trig reindentation + ;; when they are completed. + '(("else" "else" c-electric-continued-statement 0) + ("while" "while" c-electric-continued-statement 0))) + +(defvar arduino-mode-map (let ((map (c-make-inherited-keymap))) + ;; Add bindings which are only useful for Arduino + map) + "Keymap used in arduino-mode buffers.") + +(easy-menu-define arduino-menu arduino-mode-map "Arduino Mode Commands" + (cons "Arduino" (c-lang-const c-mode-menu arduino))) + +;;;###autoload +(add-to-list 'auto-mode-alist '("\\.pde\\'" . arduino-mode)) + +;;;###autoload +(defun arduino-mode () + "Major mode for editing Arduino code. + +The hook `c-mode-common-hook' is run with no args at mode +initialization, then `arduino-mode-hook'. + +Key bindings: +\\{arduino-mode-map}" + (interactive) + (kill-all-local-variables) + (c-initialize-cc-mode t) + (set-syntax-table arduino-mode-syntax-table) + (setq major-mode 'arduino-mode + mode-name "Arduino" + local-abbrev-table arduino-mode-abbrev-table + abbrev-mode t + imenu-generic-expression cc-imenu-c-generic-expression) + (use-local-map c-mode-map) + ;; `c-init-language-vars' is a macro that is expanded at compile + ;; time to a large `setq' with all the language variables and their + ;; customized values for our language. + (c-init-language-vars arduino-mode) + ;; `c-common-init' initializes most of the components of a CC Mode + ;; buffer, including setup of the mode menu, font-lock, etc. + ;; There's also a lower level routine `c-basic-common-init' that + ;; only makes the necessary initialization to get the syntactic + ;; analysis and similar things working. + (c-common-init 'arduino-mode) + (easy-menu-add arduino-menu) + (run-hooks 'c-mode-common-hook) + (run-hooks 'arduino-mode-hook) + (c-update-modeline)) + +(provide 'arduino-mode) +;;; arduino-mode.el ends here diff --git a/emacs.d/lisp/filladapt.el b/emacs.d/lisp/filladapt.el new file mode 100644 index 0000000..4ae63ab --- /dev/null +++ b/emacs.d/lisp/filladapt.el @@ -0,0 +1,981 @@ +;;; Adaptive fill +;;; Copyright (C) 1989, 1995-1998 Kyle E. Jones +;;; +;;; This program is free software; you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published by +;;; the Free Software Foundation; either version 2, or (at your option) +;;; any later version. +;;; +;;; This program is distributed in the hope that it will be useful, +;;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;;; GNU General Public License for more details. +;;; +;;; A copy of the GNU General Public License can be obtained from this +;;; program's author (send electronic mail to kyle@uunet.uu.net) or from +;;; the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA +;;; 02139, USA. +;;; +;;; Send bug reports to kyle_jones@wonderworks.com + +;; LCD Archive Entry: +;; filladapt|Kyle Jones|kyle_jones@wonderworks.com| +;; Minor mode to adaptively set fill-prefix and overload filling functions| +;; 28-February-1998|2.12|~/packages/filladapt.el| + +;; These functions enhance the default behavior of Emacs' Auto Fill +;; mode and the commands fill-paragraph, lisp-fill-paragraph, +;; fill-region-as-paragraph and fill-region. +;; +;; The chief improvement is that the beginning of a line to be +;; filled is examined and, based on information gathered, an +;; appropriate value for fill-prefix is constructed. Also the +;; boundaries of the current paragraph are located. This occurs +;; only if the fill prefix is not already non-nil. +;; +;; The net result of this is that blurbs of text that are offset +;; from left margin by asterisks, dashes, and/or spaces, numbered +;; examples, included text from USENET news articles, etc. are +;; generally filled correctly with no fuss. +;; +;; Since this package replaces existing Emacs functions, it cannot +;; be autoloaded. Save this in a file named filladapt.el in a +;; Lisp directory that Emacs knows about, byte-compile it and put +;; (require 'filladapt) +;; in your .emacs file. +;; +;; Note that in this release Filladapt mode is a minor mode and it is +;; _off_ by default. If you want it to be on by default, use +;; (setq-default filladapt-mode t) +;; +;; M-x filladapt-mode toggles Filladapt mode on/off in the current +;; buffer. +;; +;; Use +;; (add-hook 'text-mode-hook 'turn-on-filladapt-mode) +;; to have Filladapt always enabled in Text mode. +;; +;; Use +;; (add-hook 'c-mode-hook 'turn-off-filladapt-mode) +;; to have Filladapt always disabled in C mode. +;; +;; In many cases, you can extend Filladapt by adding appropriate +;; entries to the following three `defvar's. See `postscript-comment' +;; or `texinfo-comment' as a sample of what needs to be done. +;; +;; filladapt-token-table +;; filladapt-token-match-table +;; filladapt-token-conversion-table + +(and (featurep 'filladapt) + (error "filladapt cannot be loaded twice in the same Emacs session.")) + +(provide 'filladapt) + +(defvar filladapt-version "2.12" + "Version string for filladapt.") + +;; BLOB to make custom stuff work even without customize +(eval-and-compile + (condition-case () + (require 'custom) + (error nil)) + (if (and (featurep 'custom) (fboundp 'custom-declare-variable)) + nil ;; We've got what we needed + ;; We have the old custom-library, hack around it! + (defmacro defgroup (&rest args) + nil) + (defmacro defcustom (var value doc &rest args) + (` (defvar (, var) (, value) (, doc)))))) + +(defgroup filladapt nil + "Enhanced filling" + :group 'fill) + +(defvar filladapt-mode nil + "Non-nil means that Filladapt minor mode is enabled. +Use the filladapt-mode command to toggle the mode on/off.") +(make-variable-buffer-local 'filladapt-mode) + +(defcustom filladapt-mode-line-string " Filladapt" + "*String to display in the modeline when Filladapt mode is active. +Set this to nil if you don't want a modeline indicator for Filladapt." + :type 'string + :group 'filladapt) + +(defcustom filladapt-fill-column-tolerance nil + "*Tolerate filled paragraph lines ending this far from the fill column. +If any lines other than the last paragraph line end at a column +less than fill-column - filladapt-fill-column-tolerance, fill-column will +be adjusted using the filladapt-fill-column-*-fuzz variables and +the paragraph will be re-filled until the tolerance is achieved +or filladapt runs out of fuzz values to try. + +A nil value means behave normally, that is, don't try refilling +paragraphs to make filled line lengths fit within any particular +range." + :type '(choice (const nil) + integer) + :group 'filladapt) + +(defcustom filladapt-fill-column-forward-fuzz 5 + "*Try values from fill-column to fill-column plus this variable +when trying to make filled paragraph lines fall with the tolerance +range specified by filladapt-fill-column-tolerance." + :type 'integer + :group 'filladapt) + +(defcustom filladapt-fill-column-backward-fuzz 5 + "*Try values from fill-column to fill-column minus this variable +when trying to make filled paragraph lines fall with the tolerance +range specified by filladapt-fill-column-tolerance." + :type 'integer + :group 'filladapt) + +;; install on minor-mode-alist +(or (assq 'filladapt-mode minor-mode-alist) + (setq minor-mode-alist (cons (list 'filladapt-mode + 'filladapt-mode-line-string) + minor-mode-alist))) + +(defcustom filladapt-token-table + '( + ;; this must be first + ("^" beginning-of-line) + ;; Included text in news or mail replies + (">+" citation->) + ;; Included text generated by SUPERCITE. We can't hope to match all + ;; the possible variations, your mileage may vary. + ("\\(\\w\\|[0-9]\\)[^'`\"< \t\n]*>[ \t]*" supercite-citation) + ;; Lisp comments + (";+" lisp-comment) + ;; UNIX shell comments + ("#+" sh-comment) + ;; Postscript comments + ("%+" postscript-comment) + ;; C++ comments + ("///*" c++-comment) + ;; Texinfo comments + ("@c[ \t]" texinfo-comment) + ("@comment[ \t]" texinfo-comment) + ;; Bullet types. + ;; + ;; LaTex \item + ;; + ("\\\\item[ \t]" bullet) + ;; + ;; 1. xxxxx + ;; xxxxx + ;; + ("[0-9]+\\.[ \t]" bullet) + ;; + ;; 2.1.3 xxxxx xx x xx x + ;; xxx + ;; + ("[0-9]+\\(\\.[0-9]+\\)+[ \t]" bullet) + ;; + ;; a. xxxxxx xx + ;; xxx xxx + ;; + ("[A-Za-z]\\.[ \t]" bullet) + ;; + ;; 1) xxxx x xx x xx or (1) xx xx x x xx xx + ;; xx xx xxxx xxx xx x x xx x + ;; + ("(?[0-9]+)[ \t]" bullet) + ;; + ;; a) xxxx x xx x xx or (a) xx xx x x xx xx + ;; xx xx xxxx xxx xx x x xx x + ;; + ("(?[A-Za-z])[ \t]" bullet) + ;; + ;; 2a. xx x xxx x x xxx + ;; xxx xx x xx x + ;; + ("[0-9]+[A-Za-z]\\.[ \t]" bullet) + ;; + ;; 1a) xxxx x xx x xx or (1a) xx xx x x xx xx + ;; xx xx xxxx xxx xx x x xx x + ;; + ("(?[0-9]+[A-Za-z])[ \t]" bullet) + ;; + ;; - xx xxx xxxx or * xx xx x xxx xxx + ;; xxx xx xx x xxx x xx x x x + ;; + ("[-~*+]+[ \t]" bullet) + ;; + ;; o xx xxx xxxx xx x xx xxx x xxx xx x xxx + ;; xxx xx xx + ;; + ("o[ \t]" bullet) + ;; don't touch + ("[ \t]+" space) + ("$" end-of-line) + ) + "Table of tokens filladapt knows about. +Format is + + ((REGEXP SYM) ...) + +filladapt uses this table to build a tokenized representation of +the beginning of the current line. Each REGEXP is matched +against the beginning of the line until a match is found. +Matching is done case-sensitively. The corresponding SYM is +added to the list, point is moved to (match-end 0) and the +process is repeated. The process ends when there is no REGEXP in +the table that matches what is at point." + :type '(repeat (list regexp symbol)) + :group 'filladapt) + +(defcustom filladapt-not-token-table + '( + "[Ee]\\.g\\.[ \t,]" + "[Ii]\\.e\\.[ \t,]" + ;; end-of-line isn't a token if whole line is empty + "^$" + ) + "List of regexps that can never be a token. +Before trying the regular expressions in filladapt-token-table, +the regexps in this list are tried. If any regexp in this list +matches what is at point then the token generator gives up and +doesn't try any of the regexps in filladapt-token-table. + +Regexp matching is done case-sensitively." + :type '(repeat regexp) + :group 'filladapt) + +(defcustom filladapt-token-match-table + '( + (citation-> citation->) + (supercite-citation supercite-citation) + (lisp-comment lisp-comment) + (sh-comment sh-comment) + (postscript-comment postscript-comment) + (c++-comment c++-comment) + (texinfo-comment texinfo-comment) + (bullet) + (space bullet space) + (beginning-of-line beginning-of-line) + ) + "Table describing what tokens a certain token will match. + +To decide whether a line belongs in the current paragraph, +filladapt creates a token list for the fill prefix of both lines. +Tokens and the columns where tokens end are compared. This table +specifies what a certain token will match. + +Table format is + + (SYM [SYM1 [SYM2 ...]]) + +The first symbol SYM is the token, subsequent symbols are the +tokens that SYM will match." + :type '(repeat (repeat symbol)) + :group 'filladapt) + +(defcustom filladapt-token-match-many-table + '( + space + ) + "List of tokens that can match multiple tokens. +If one of these tokens appears in a token list, it will eat all +matching tokens in a token list being matched against it until it +encounters a token that doesn't match or a token that ends on +a greater column number." + :type '(repeat symbol) + :group 'filladapt) + +(defcustom filladapt-token-paragraph-start-table + '( + bullet + ) + "List of tokens that indicate the start of a paragraph. +If parsing a line generates a token list containing one of +these tokens, then the line is considered to be the start of a +paragraph." + :type '(repeat symbol) + :group 'filladapt) + +(defcustom filladapt-token-conversion-table + '( + (citation-> . exact) + (supercite-citation . exact) + (lisp-comment . exact) + (sh-comment . exact) + (postscript-comment . exact) + (c++-comment . exact) + (texinfo-comment . exact) + (bullet . spaces) + (space . exact) + (end-of-line . exact) + ) + "Table that specifies how to convert a token into a fill prefix. +Table format is + + ((SYM . HOWTO) ...) + +SYM is the symbol naming the token to be converted. +HOWTO specifies how to do the conversion. + `exact' means copy the token's string directly into the fill prefix. + `spaces' means convert all characters in the token string that are + not a TAB or a space into spaces and copy the resulting string into + the fill prefix." + :type '(repeat (cons symbol (choice (const exact) + (const spaces)))) + :group 'filladapt) + +(defvar filladapt-function-table + (let ((assoc-list + (list (cons 'fill-paragraph (symbol-function 'fill-paragraph)) + (cons 'fill-region (symbol-function 'fill-region)) + (cons 'fill-region-as-paragraph + (symbol-function 'fill-region-as-paragraph)) + (cons 'do-auto-fill (symbol-function 'do-auto-fill))))) + ;; v18 Emacs doesn't have lisp-fill-paragraph + (if (fboundp 'lisp-fill-paragraph) + (nconc assoc-list + (list (cons 'lisp-fill-paragraph + (symbol-function 'lisp-fill-paragraph))))) + assoc-list ) + "Table containing the old function definitions that filladapt usurps.") + +(defcustom filladapt-fill-paragraph-post-hook nil + "Hooks run after filladapt runs fill-paragraph." + :type 'hook + :group 'filladapt) + +(defvar filladapt-inside-filladapt nil + "Non-nil if the filladapt version of a fill function executing. +Currently this is only checked by the filladapt version of +fill-region-as-paragraph to avoid this infinite recursion: + + fill-region-as-paragraph -> fill-paragraph -> fill-region-as-paragraph ...") + +(defcustom filladapt-debug nil + "Non-nil means filladapt debugging is enabled. +Use the filladapt-debug command to turn on debugging. + +With debugging enabled, filladapt will + + a. display the proposed indentation with the tokens highlighted + using filladapt-debug-indentation-face-1 and + filladapt-debug-indentation-face-2. + b. display the current paragraph using the face specified by + filladapt-debug-paragraph-face." + :type 'boolean + :group 'filladapt) + +(if filladapt-debug + (add-hook 'post-command-hook 'filladapt-display-debug-info-maybe)) + +(defvar filladapt-debug-indentation-face-1 'highlight + "Face used to display the indentation when debugging is enabled.") + +(defvar filladapt-debug-indentation-face-2 'secondary-selection + "Another face used to display the indentation when debugging is enabled.") + +(defvar filladapt-debug-paragraph-face 'bold + "Face used to display the current paragraph when debugging is enabled.") + +(defvar filladapt-debug-indentation-extents nil) +(make-variable-buffer-local 'filladapt-debug-indentation-extents) +(defvar filladapt-debug-paragraph-extent nil) +(make-variable-buffer-local 'filladapt-debug-paragraph-extent) + +;; kludge city, see references in code. +(defvar filladapt-old-line-prefix) + +(defun do-auto-fill () + (catch 'done + (if (and filladapt-mode (null fill-prefix)) + (save-restriction + (let ((paragraph-ignore-fill-prefix nil) + ;; if the user wanted this stuff, they probably + ;; wouldn't be using filladapt-mode. + (adaptive-fill-mode nil) + (adaptive-fill-regexp nil) + ;; need this or Emacs 19 ignores fill-prefix when + ;; inside a comment. + (comment-multi-line t) + (filladapt-inside-filladapt t) + fill-prefix retval) + (if (filladapt-adapt nil nil) + (progn + (setq retval (filladapt-funcall 'do-auto-fill)) + (throw 'done retval)))))) + (filladapt-funcall 'do-auto-fill))) + +(defun filladapt-fill-paragraph (function arg) + (catch 'done + (if (and filladapt-mode (null fill-prefix)) + (save-restriction + (let ((paragraph-ignore-fill-prefix nil) + ;; if the user wanted this stuff, they probably + ;; wouldn't be using filladapt-mode. + (adaptive-fill-mode nil) + (adaptive-fill-regexp nil) + ;; need this or Emacs 19 ignores fill-prefix when + ;; inside a comment. + (comment-multi-line t) + fill-prefix retval) + (if (filladapt-adapt t nil) + (progn + (if filladapt-fill-column-tolerance + (let* ((low (- fill-column + filladapt-fill-column-backward-fuzz)) + (high (+ fill-column + filladapt-fill-column-forward-fuzz)) + (old-fill-column fill-column) + (fill-column fill-column) + (lim (- high low)) + (done nil) + (sign 1) + (delta 0)) + (while (not done) + (setq retval (filladapt-funcall function arg)) + (if (filladapt-paragraph-within-fill-tolerance) + (setq done 'success) + (setq delta (1+ delta) + sign (* sign -1) + fill-column (+ fill-column (* delta sign))) + (while (and (<= delta lim) + (or (< fill-column low) + (> fill-column high))) + (setq delta (1+ delta) + sign (* sign -1) + fill-column (+ fill-column + (* delta sign)))) + (setq done (> delta lim)))) + ;; if the paragraph lines never fell + ;; within the tolerances, refill using + ;; the old fill-column. + (if (not (eq done 'success)) + (let ((fill-column old-fill-column)) + (setq retval (filladapt-funcall function arg))))) + (setq retval (filladapt-funcall function arg))) + (run-hooks 'filladapt-fill-paragraph-post-hook) + (throw 'done retval)))))) + ;; filladapt-adapt failed, so do fill-paragraph normally. + (filladapt-funcall function arg))) + +(defun fill-paragraph (arg) + "Fill paragraph at or after point. Prefix arg means justify as well. + +(This function has been overloaded with the `filladapt' version.) + +If `sentence-end-double-space' is non-nil, then period followed by one +space does not end a sentence, so don't break a line there. + +If `fill-paragraph-function' is non-nil, we call it (passing our +argument to it), and if it returns non-nil, we simply return its value." + (interactive "*P") + (let ((filladapt-inside-filladapt t)) + (filladapt-fill-paragraph 'fill-paragraph arg))) + +(defun lisp-fill-paragraph (&optional arg) + "Like \\[fill-paragraph], but handle Emacs Lisp comments. + +(This function has been overloaded with the `filladapt' version.) + +If any of the current line is a comment, fill the comment or the +paragraph of it that point is in, preserving the comment's indentation +and initial semicolons." + (interactive "*P") + (let ((filladapt-inside-filladapt t)) + (filladapt-fill-paragraph 'lisp-fill-paragraph arg))) + +(defun fill-region-as-paragraph (beg end &optional justify + nosqueeze squeeze-after) + "Fill the region as one paragraph. + +(This function has been overloaded with the `filladapt' version.) + +It removes any paragraph breaks in the region and extra newlines at the end, +indents and fills lines between the margins given by the +`current-left-margin' and `current-fill-column' functions. +It leaves point at the beginning of the line following the paragraph. + +Normally performs justification according to the `current-justification' +function, but with a prefix arg, does full justification instead. + +From a program, optional third arg JUSTIFY can specify any type of +justification. Fourth arg NOSQUEEZE non-nil means not to make spaces +between words canonical before filling. Fifth arg SQUEEZE-AFTER, if non-nil, +means don't canonicalize spaces before that position. + +If `sentence-end-double-space' is non-nil, then period followed by one +space does not end a sentence, so don't break a line there." + (interactive "*r\nP") + (if (and filladapt-mode (not filladapt-inside-filladapt)) + (save-restriction + (narrow-to-region beg end) + (let ((filladapt-inside-filladapt t) + line-start last-token) + (goto-char beg) + (while (equal (char-after (point)) ?\n) + (delete-char 1)) + (end-of-line) + (while (zerop (forward-line)) + (if (setq last-token + (car (filladapt-tail (filladapt-parse-prefixes)))) + (progn + (setq line-start (point)) + (move-to-column (nth 1 last-token)) + (delete-region line-start (point)))) + ;; Dance... + ;; + ;; Do this instead of (delete-char -1) to keep + ;; markers on the correct side of the whitespace. + (goto-char (1- (point))) + (insert " ") + (delete-char 1) + + (end-of-line)) + (goto-char beg) + (fill-paragraph justify)) + ;; In XEmacs 19.12 and Emacs 18.59 fill-region relies on + ;; fill-region-as-paragraph to do this. If we don't do + ;; it, fill-region will spin in an endless loop. + (goto-char (point-max))) + (condition-case nil + ;; five args for Emacs 19.31 + (filladapt-funcall 'fill-region-as-paragraph beg end + justify nosqueeze squeeze-after) + (wrong-number-of-arguments + (condition-case nil + ;; four args for Emacs 19.29 + (filladapt-funcall 'fill-region-as-paragraph beg end + justify nosqueeze) + ;; three args for the rest of the world. + (wrong-number-of-arguments + (filladapt-funcall 'fill-region-as-paragraph beg end justify))))))) + +(defun fill-region (beg end &optional justify nosqueeze to-eop) + "Fill each of the paragraphs in the region. + +(This function has been overloaded with the `filladapt' version.) + +Prefix arg (non-nil third arg, if called from program) means justify as well. + +Noninteractively, fourth arg NOSQUEEZE non-nil means to leave +whitespace other than line breaks untouched, and fifth arg TO-EOP +non-nil means to keep filling to the end of the paragraph (or next +hard newline, if `use-hard-newlines' is on). + +If `sentence-end-double-space' is non-nil, then period followed by one +space does not end a sentence, so don't break a line there." + (interactive "*r\nP") + (if (and filladapt-mode (not filladapt-inside-filladapt)) + (save-restriction + (narrow-to-region beg end) + (let ((filladapt-inside-filladapt t) + start) + (goto-char beg) + (while (not (eobp)) + (setq start (point)) + (while (and (not (eobp)) (not (filladapt-parse-prefixes))) + (forward-line 1)) + (if (not (equal start (point))) + (progn + (save-restriction + (narrow-to-region start (point)) + (fill-region start (point) justify nosqueeze to-eop) + (goto-char (point-max))) + (if (and (not (bolp)) (not (eobp))) + (forward-line 1)))) + (if (filladapt-parse-prefixes) + (progn + (save-restriction + ;; for the clipping region + (filladapt-adapt t t) + (fill-paragraph justify) + (goto-char (point-max))) + (if (and (not (bolp)) (not (eobp))) + (forward-line 1))))))) + (condition-case nil + (filladapt-funcall 'fill-region beg end justify nosqueeze to-eop) + (wrong-number-of-arguments + (condition-case nil + (filladapt-funcall 'fill-region beg end justify nosqueeze) + (wrong-number-of-arguments + (filladapt-funcall 'fill-region beg end justify))))))) + +(defvar zmacs-region-stays) ; for XEmacs + +(defun filladapt-mode (&optional arg) + "Toggle Filladapt minor mode. +With arg, turn Filladapt mode on iff arg is positive. When +Filladapt mode is enabled, auto-fill-mode and the fill-paragraph +command are both smarter about guessing a proper fill-prefix and +finding paragraph boundaries when bulleted and indented lines and +paragraphs are used." + (interactive "P") + ;; don't deactivate the region. + (setq zmacs-region-stays t) + (setq filladapt-mode (or (and arg (> (prefix-numeric-value arg) 0)) + (and (null arg) (null filladapt-mode)))) + (if (fboundp 'force-mode-line-update) + (force-mode-line-update) + (set-buffer-modified-p (buffer-modified-p)))) + +(defun turn-on-filladapt-mode () + "Unconditionally turn on Filladapt mode in the current buffer." + (filladapt-mode 1)) + +(defun turn-off-filladapt-mode () + "Unconditionally turn off Filladapt mode in the current buffer." + (filladapt-mode -1)) + +(defun filladapt-funcall (function &rest args) + "Call the old definition of a function that filladapt has usurped." + (apply (cdr (assoc function filladapt-function-table)) args)) + +(defun filladapt-paragraph-start (list) + "Returns non-nil if LIST contains a paragraph starting token. +LIST should be a token list as returned by filladapt-parse-prefixes." + (catch 'done + (while list + (if (memq (car (car list)) filladapt-token-paragraph-start-table) + (throw 'done t)) + (setq list (cdr list))))) + +(defun filladapt-parse-prefixes () + "Parse all the tokens after point and return a list of them. +The tokens regular expressions are specified in +filladapt-token-table. The list returned is of this form + + ((SYM COL STRING) ...) + +SYM is a token symbol as found in filladapt-token-table. +COL is the column at which the token ended. +STRING is the token's text." + (save-excursion + (let ((token-list nil) + (done nil) + (old-point (point)) + (case-fold-search nil) + token-table not-token-table moved) + (catch 'done + (while (not done) + (setq not-token-table filladapt-not-token-table) + (while not-token-table + (if (looking-at (car not-token-table)) + (throw 'done t)) + (setq not-token-table (cdr not-token-table))) + (setq token-table filladapt-token-table + done t) + (while token-table + (if (null (looking-at (car (car token-table)))) + (setq token-table (cdr token-table)) + (goto-char (match-end 0)) + (setq token-list (cons (list (nth 1 (car token-table)) + (current-column) + (buffer-substring + (match-beginning 0) + (match-end 0))) + token-list) + moved (not (eq (point) old-point)) + token-table (if moved nil (cdr token-table)) + done (not moved) + old-point (point)))))) + (nreverse token-list)))) + +(defun filladapt-tokens-match-p (list1 list2) + "Compare two token lists and return non-nil if they match, nil otherwise. +The lists are walked through in lockstep, comparing tokens. + +When two tokens A and B are compared, they are considered to +match if + + 1. A appears in B's list of matching tokens or + B appears in A's list of matching tokens +and + 2. A and B both end at the same column + or + A can match multiple tokens and ends at a column > than B + or + B can match multiple tokens and ends at a column > than A + +In the case where the end columns differ the list pointer for the +token with the greater end column is not moved forward, which +allows its current token to be matched against the next token in +the other list in the next iteration of the matching loop. + +All tokens must be matched in order for the lists to be considered +matching." + (let ((matched t) + (done nil)) + (while (and (not done) list1 list2) + (let* ((token1 (car (car list1))) + (token1-matches-many-p + (memq token1 filladapt-token-match-many-table)) + (token1-matches (cdr (assq token1 filladapt-token-match-table))) + (token1-endcol (nth 1 (car list1))) + (token2 (car (car list2))) + (token2-matches-many-p + (memq token2 filladapt-token-match-many-table)) + (token2-matches (cdr (assq token2 filladapt-token-match-table))) + (token2-endcol (nth 1 (car list2))) + (tokens-match (or (memq token1 token2-matches) + (memq token2 token1-matches)))) + (cond ((not tokens-match) + (setq matched nil + done t)) + ((and token1-matches-many-p token2-matches-many-p) + (cond ((= token1-endcol token2-endcol) + (setq list1 (cdr list1) + list2 (cdr list2))) + ((< token1-endcol token2-endcol) + (setq list1 (cdr list1))) + (t + (setq list2 (cdr list2))))) + (token1-matches-many-p + (cond ((= token1-endcol token2-endcol) + (setq list1 (cdr list1) + list2 (cdr list2))) + ((< token1-endcol token2-endcol) + (setq matched nil + done t)) + (t + (setq list2 (cdr list2))))) + (token2-matches-many-p + (cond ((= token1-endcol token2-endcol) + (setq list1 (cdr list1) + list2 (cdr list2))) + ((< token2-endcol token1-endcol) + (setq matched nil + done t)) + (t + (setq list1 (cdr list1))))) + ((= token1-endcol token2-endcol) + (setq list1 (cdr list1) + list2 (cdr list2))) + (t + (setq matched nil + done t))))) + (and matched (null list1) (null list2)) )) + +(defun filladapt-make-fill-prefix (list) + "Build a fill-prefix for a token LIST. +filladapt-token-conversion-table specifies how this is done." + (let ((prefix-list nil) + (conversion-spec nil)) + (while list + (setq conversion-spec (cdr (assq (car (car list)) + filladapt-token-conversion-table))) + (cond ((eq conversion-spec 'spaces) + (setq prefix-list + (cons + (filladapt-convert-to-spaces (nth 2 (car list))) + prefix-list))) + ((eq conversion-spec 'exact) + (setq prefix-list + (cons + (nth 2 (car list)) + prefix-list)))) + (setq list (cdr list))) + (apply (function concat) (nreverse prefix-list)) )) + +(defun filladapt-paragraph-within-fill-tolerance () + (catch 'done + (save-excursion + (let ((low (- fill-column filladapt-fill-column-tolerance)) + (shortline nil)) + (goto-char (point-min)) + (while (not (eobp)) + (if shortline + (throw 'done nil) + (end-of-line) + (setq shortline (< (current-column) low)) + (forward-line 1))) + t )))) + +(defun filladapt-convert-to-spaces (string) + "Return a copy of STRING, with all non-tabs and non-space changed to spaces." + (let ((i 0) + (space-list '(?\ ?\t)) + (space ?\ ) + (lim (length string))) + (setq string (copy-sequence string)) + (while (< i lim) + (if (not (memq (aref string i) space-list)) + (aset string i space)) + (setq i (1+ i))) + string )) + +(defun filladapt-adapt (paragraph debugging) + "Set fill-prefix based on the contents of the current line. + +If the first arg PARAGRAPH is non-nil, also set a clipping region +around the current paragraph. + +If the second arg DEBUGGING is non-nil, don't do the kludge that's +necessary to make certain paragraph fills work properly." + (save-excursion + (beginning-of-line) + (let ((token-list (filladapt-parse-prefixes)) + curr-list done) + (if (null token-list) + nil + (setq fill-prefix (filladapt-make-fill-prefix token-list)) + (if paragraph + (let (beg end) + (if (filladapt-paragraph-start token-list) + (setq beg (point)) + (save-excursion + (setq done nil) + (while (not done) + (cond ((not (= 0 (forward-line -1))) + (setq done t + beg (point))) + ((not (filladapt-tokens-match-p + token-list + (setq curr-list (filladapt-parse-prefixes)))) + (forward-line 1) + (setq done t + beg (point))) + ((filladapt-paragraph-start curr-list) + (setq done t + beg (point))))))) + (save-excursion + (setq done nil) + (while (not done) + (cond ((not (= 0 (progn (end-of-line) (forward-line 1)))) + (setq done t + end (point))) + ((not (filladapt-tokens-match-p + token-list + (setq curr-list (filladapt-parse-prefixes)))) + (setq done t + end (point))) + ((filladapt-paragraph-start curr-list) + (setq done t + end (point)))))) + (narrow-to-region beg end) + ;; Multiple spaces after the bullet at the start of + ;; a hanging list paragraph get squashed by + ;; fill-paragraph. We kludge around this by + ;; replacing the line prefix with the fill-prefix + ;; used by the rest of the lines in the paragraph. + ;; fill-paragraph will not alter the fill prefix so + ;; we win. The post hook restores the old line prefix + ;; after fill-paragraph has been called. + (if (and paragraph (not debugging)) + (let (col) + (setq col (nth 1 (car (filladapt-tail token-list)))) + (goto-char (point-min)) + (move-to-column col) + (setq filladapt-old-line-prefix + (buffer-substring (point-min) (point))) + (delete-region (point-min) (point)) + (insert fill-prefix) + (add-hook 'filladapt-fill-paragraph-post-hook + 'filladapt-cleanup-kludge-at-point-min))))) + t )))) + +(defun filladapt-cleanup-kludge-at-point-min () + "Cleanup the paragraph fill kludge. +See filladapt-adapt." + (save-excursion + (goto-char (point-min)) + (insert filladapt-old-line-prefix) + (delete-char (length fill-prefix)) + (remove-hook 'filladapt-fill-paragraph-post-hook + 'filladapt-cleanup-kludge-at-point-min))) + +(defun filladapt-tail (list) + "Returns the last cons in LIST." + (if (null list) + nil + (while (consp (cdr list)) + (setq list (cdr list))) + list )) + +(defun filladapt-delete-extent (e) + (if (fboundp 'delete-extent) + (delete-extent e) + (delete-overlay e))) + +(defun filladapt-make-extent (beg end) + (if (fboundp 'make-extent) + (make-extent beg end) + (make-overlay beg end))) + +(defun filladapt-set-extent-endpoints (e beg end) + (if (fboundp 'set-extent-endpoints) + (set-extent-endpoints e beg end) + (move-overlay e beg end))) + +(defun filladapt-set-extent-property (e prop val) + (if (fboundp 'set-extent-property) + (set-extent-property e prop val) + (overlay-put e prop val))) + +(defun filladapt-debug () + "Toggle filladapt debugging on/off in the current buffer." +;; (interactive) + (make-local-variable 'filladapt-debug) + (setq filladapt-debug (not filladapt-debug)) + (if (null filladapt-debug) + (progn + (mapcar (function (lambda (e) (filladapt-set-extent-endpoints e 1 1))) + filladapt-debug-indentation-extents) + (if filladapt-debug-paragraph-extent + (progn + (filladapt-delete-extent filladapt-debug-paragraph-extent) + (setq filladapt-debug-paragraph-extent nil))))) + (add-hook 'post-command-hook 'filladapt-display-debug-info-maybe)) + +(defun filladapt-display-debug-info-maybe () + (cond ((null filladapt-debug) nil) + (fill-prefix nil) + (t + (if (null filladapt-debug-paragraph-extent) + (let ((e (filladapt-make-extent 1 1))) + (filladapt-set-extent-property e 'detachable nil) + (filladapt-set-extent-property e 'evaporate nil) + (filladapt-set-extent-property e 'face + filladapt-debug-paragraph-face) + (setq filladapt-debug-paragraph-extent e))) + (save-excursion + (save-restriction + (let ((ei-list filladapt-debug-indentation-extents) + (ep filladapt-debug-paragraph-extent) + (face filladapt-debug-indentation-face-1) + fill-prefix token-list) + (if (null (filladapt-adapt t t)) + (progn + (filladapt-set-extent-endpoints ep 1 1) + (while ei-list + (filladapt-set-extent-endpoints (car ei-list) 1 1) + (setq ei-list (cdr ei-list)))) + (filladapt-set-extent-endpoints ep (point-min) (point-max)) + (beginning-of-line) + (setq token-list (filladapt-parse-prefixes)) + (message "(%s)" (mapconcat (function + (lambda (q) (symbol-name (car q)))) + token-list + " ")) + (while token-list + (if ei-list + (setq e (car ei-list) + ei-list (cdr ei-list)) + (setq e (filladapt-make-extent 1 1)) + (filladapt-set-extent-property e 'detachable nil) + (filladapt-set-extent-property e 'evaporate nil) + (setq filladapt-debug-indentation-extents + (cons e filladapt-debug-indentation-extents))) + (filladapt-set-extent-property e 'face face) + (filladapt-set-extent-endpoints e (point) + (progn + (move-to-column + (nth 1 + (car token-list))) + (point))) + (if (eq face filladapt-debug-indentation-face-1) + (setq face filladapt-debug-indentation-face-2) + (setq face filladapt-debug-indentation-face-1)) + (setq token-list (cdr token-list))) + (while ei-list + (filladapt-set-extent-endpoints (car ei-list) 1 1) + (setq ei-list (cdr ei-list)))))))))) diff --git a/emacs.d/lisp/jshint-mode/LICENSE b/emacs.d/lisp/jshint-mode/LICENSE new file mode 100644 index 0000000..247ec3e --- /dev/null +++ b/emacs.d/lisp/jshint-mode/LICENSE @@ -0,0 +1,23 @@ +jshint-mode copyright (c) 2011 Dale Harvey (arandomurl.com) +lintnode copyright (c) 2010 Kevin Turner (keturn.net) +fullnodejs.js copyright (c) 2002 Douglas Crockford (www.JSLint.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/emacs.d/lisp/jshint-mode/README.markdown b/emacs.d/lisp/jshint-mode/README.markdown new file mode 100644 index 0000000..0db9826 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/README.markdown @@ -0,0 +1,38 @@ +jshint-mode +======= + +Integrate [JSHint](http://jshint.com) into Emacs via a [node.js](http://nodejs.org) server. + +![example](https://github.com/daleharvey/jshint-mode/raw/master/example.png) + +jshint-mode was heavily inspired by Kevin Turners [lintnode](https://github.com/keturn/lintnode). + +Building +======== + +Install via [npm](http://npmjs.org/) + + $ npm install jshint-mode + +or you can [download](https://github.com/daleharvey/jshint-mode/tarball/master) or clone + + $ git clone git://github.com/daleharvey/jshint-mode.git + +Usage +===== + +Add the configuration to your .emacs config file: + + $jshint-emacs-path >> ~/.emacs + +or if you downloaded manually: + + (add-to-list 'load-path "~/path/to/jshint-mode") + (require 'flymake-jshint) + (add-hook 'javascript-mode-hook + (lambda () (flymake-mode t))) + +You can use M-x flymake-mode to turn flymake of and on, if you want to turn it on be fault, add the following to your .emacs + + ;; Turns on flymake for all files which have a flymake mode + (add-hook 'find-file-hook 'flymake-find-file-hook) diff --git a/emacs.d/lisp/jshint-mode/example.png b/emacs.d/lisp/jshint-mode/example.png new file mode 100644 index 0000000..2ce89c0 Binary files /dev/null and b/emacs.d/lisp/jshint-mode/example.png differ diff --git a/emacs.d/lisp/jshint-mode/flymake-jshint.el b/emacs.d/lisp/jshint-mode/flymake-jshint.el new file mode 100644 index 0000000..577ce40 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/flymake-jshint.el @@ -0,0 +1,90 @@ +;; adapted from http://www.emacswiki.org/emacs/FlymakeJavaScript +;; +;; Installation: +;; +;; (add-to-list 'load-path "~/lib/jshint-mode") +;; (require 'flymake-jshint) +;; (add-hook 'javascript-mode-hook +;; (lambda () (flymake-mode t))) +;; +;; run M-x flymake-mode to turn flymake on and off +;; + +(require 'flymake) + +(defcustom jshint-mode-mode "jshint" + "Can use either jshint or jslint" + :type 'string + :group 'flymake-jshint) + +(defcustom jshint-mode-node-program "node" + "The program name to invoke node.js." + :type 'string + :group 'flymake-jshint) + +(defcustom jshint-mode-location (file-name-directory load-file-name) + "The directory jshint-mode.js may be found in." + :type 'string + :group 'flymake-jshint) + +(defcustom jshint-mode-port 3003 + "The port the jshint-mode server runs on." + :type 'integer + :group 'flymake-jshint) + +(defcustom jshint-mode-host "127.0.0.1" + "The host the jshint-mode server runs on." + :type 'string + :group 'flymake-jshint) + +(setq jshint-process "jshint-mode-server") +(setq jshint-buffer "*jshint-mode*") + +(defun jshint-mode-init () + "Start the jshint-mode server." + (interactive) + (if (eq (process-status jshint-process) 'run) + 'started + (start-process + jshint-process + jshint-buffer + jshint-mode-node-program + (expand-file-name (concat jshint-mode-location "/jshint-mode.js")) + "--host" jshint-mode-host + "--port" (number-to-string jshint-mode-port)) + (set-process-query-on-exit-flag (get-process jshint-process) nil) + (message + (concat "jshint server has started on " jshint-mode-host + (number-to-string jshint-mode-port))) + 'starting + )) + +(defun jshint-mode-stop () + "Stop the jshint-mode server." + (interactive) + (delete-process jshint-process)) + +(defun flymake-jshint-init () + (if (eq (jshint-mode-init) 'started) + (let* ((temp-file (flymake-init-create-temp-buffer-copy 'flymake-create-temp-inplace)) + (local-file (file-relative-name temp-file + (file-name-directory buffer-file-name))) + (jshint-url (format "http://%s:%d/check" jshint-mode-host jshint-mode-port))) + (list "curl" (list "--form" (format "source=<%s" local-file) + "--form" (format "filename=%s" local-file) + "--form" (format "mode=%s" jshint-mode-mode) + jshint-url))))) + +(setq flymake-allowed-file-name-masks + (cons '(".+\\.js$" + flymake-jshint-init + flymake-simple-cleanup + flymake-get-real-file-name) + flymake-allowed-file-name-masks)) + +(setq flymake-err-line-patterns + (cons '("^Lint at line \\([[:digit:]]+\\) character \\([[:digit:]]+\\): \\(.+\\)$" + nil 1 2 3) + flymake-err-line-patterns)) + +(provide 'flymake-jshint) diff --git a/emacs.d/lisp/jshint-mode/jshint-emacs.js b/emacs.d/lisp/jshint-mode/jshint-emacs.js new file mode 100755 index 0000000..d86cb71 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/jshint-emacs.js @@ -0,0 +1,8 @@ +#!/usr/bin/env node + +var conf = '\n(add-to-list \'load-path "' + __dirname + '")\n' + + '(require \'flymake-jshint)\n' + + '(add-hook \'javascript-mode-hook\n' + + ' (lambda () (flymake-mode t)))'; + +console.log(conf); \ No newline at end of file diff --git a/emacs.d/lisp/jshint-mode/jshint-mode.js b/emacs.d/lisp/jshint-mode/jshint-mode.js new file mode 100644 index 0000000..f3005f7 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/jshint-mode.js @@ -0,0 +1,73 @@ +/* HTTP interface to JSHint. + + curl --form source=". + + var myReport = JSHINT.report(limited); + + If limited is true, then the report will be limited to only errors. + + You can request a data structure which contains JSHint's results. + + var myData = JSHINT.data(); + + It returns a structure with this form: + + { + errors: [ + { + line: NUMBER, + character: NUMBER, + reason: STRING, + evidence: STRING + } + ], + functions: [ + name: STRING, + line: NUMBER, + last: NUMBER, + param: [ + STRING + ], + closure: [ + STRING + ], + var: [ + STRING + ], + exception: [ + STRING + ], + outer: [ + STRING + ], + unused: [ + STRING + ], + global: [ + STRING + ], + label: [ + STRING + ] + ], + globals: [ + STRING + ], + member: { + STRING: NUMBER + }, + unuseds: [ + { + name: STRING, + line: NUMBER + } + ], + implieds: [ + { + name: STRING, + line: NUMBER + } + ], + urls: [ + STRING + ], + json: BOOLEAN + } + + Empty arrays will not be included. + +*/ + +/*jshint + evil: true, nomen: false, onevar: false, regexp: false, strict: true, boss: true +*/ + +/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%", + "(begin)", "(breakage)", "(context)", "(error)", "(global)", + "(identifier)", "(last)", "(line)", "(loopage)", "(name)", "(onevar)", + "(params)", "(scope)", "(statement)", "(verb)", "*", "+", "++", "-", + "--", "\/", "<", "<=", "==", "===", ">", ">=", $, ADSAFE, __filename, __dirname, + ActiveXObject, Array, Boolean, Buffer, COM, CScript, Canvas, CustomAnimation, + Date, Debug, E, Enumerator, Error, EvalError, FadeAnimation, Flash, + FormField, Frame, Function, HotKey, Image, JSON, LN10, LN2, LOG10E, + LOG2E, MAX_VALUE, MIN_VALUE, Math, MenuItem, MoveAnimation, + NEGATIVE_INFINITY, Number, Object, Option, PI, POSITIVE_INFINITY, Point, + RangeError, Rectangle, ReferenceError, RegExp, ResizeAnimation, + RotateAnimation, SQRT1_2, SQRT2, ScrollBar, String, Style, SyntaxError, + System, Text, TextArea, Timer, TypeError, URIError, URL, VBArray, + WScript, Web, Window, XMLDOM, XMLHttpRequest, "\\", a, abbr, acronym, + activeborder, activecaption, addEventListener, address, adsafe, alert, + aliceblue, all, animator, antiquewhite, appleScript, applet, apply, + approved, appworkspace, applicationCache, aqua, aquamarine, area, arguments, + arity, article, asi, aside, audio, autocomplete, azure, b, background, + "background-attachment", "background-color", "background-image", + "background-position", "background-repeat", base, bdo, beep, beige, big, + bisque, bitwise, black, blanchedalmond, block, blockquote, blue, + blueviolet, blur, body, boolOptions, border, "border-bottom", "border-bottom-color", + "border-bottom-style", "border-bottom-width", "border-collapse", + "border-color", "border-left", "border-left-color", "border-left-style", + "border-left-width", "border-right", "border-right-color", + "border-right-style", "border-right-width", "border-spacing", + "border-style", "border-top", "border-top-color", "border-top-style", + "border-top-width", "border-width", bottom, boss, br, braille, brown, browser, + burlywood, button, buttonface, buttonhighlight, buttonshadow, + buttontext, bytesToUIString, c, cadetblue, call, callee, caller, canvas, + cap, caption, "caption-side", captiontext, cases, center, charAt, + charCodeAt, character, chartreuse, chocolate, chooseColor, chooseFile, + chooseFolder, cite, clear, clearInterval, clearTimeout, clip, close, + closeWidget, closed, closure, cm, code, col, colgroup, color, command, + comment, condition, confirm, console, constructor, content, + convertPathToHFS, convertPathToPlatform, coral, cornflowerblue, + cornsilk, couch, "counter-increment", "counter-reset", create, crimson, + css, curly, cursor, cyan, d, darkblue, darkcyan, darkgoldenrod, darkgray, + darkgreen, darkkhaki, darkmagenta, darkolivegreen, darkorange, darkorchid, + darkred, darksalmon, darkseagreen, darkslateblue, darkslategray, darkturquoise, + darkviolet, data, datalist, dd, debug, decodeURI, decodeURIComponent, + deeppink, deepskyblue, defaultStatus, defineClass, del, deserialize, + details, devel, dfn, dialog, dimgray, dir, direction, display, div, dl, + document, dodgerblue, dt, edition, else, em, embed, embossed, emit, empty, + "empty-cells", encodeURI, encodeURIComponent, entityify, eqeqeq, errors, + es5, escape, eval, event, evidence, evil, ex, exception, exec, exps, exports, + fieldset, figure, filesystem, FileReader, firebrick, first, float, floor, + floralwhite, focus, focusWidget, font, "font-family", "font-size", + "font-size-adjust", "font-stretch", "font-style", "font-variant", + "font-weight", footer, forestgreen, forin, form, fragment, frame, + frames, frameset, from, fromCharCode, fuchsia, fud, funct, function, + functions, g, gainsboro, gc, getComputedStyle, getRow, ghostwhite, GLOBAL, global, + globals, gold, goldenrod, gray, graytext, green, greenyellow, h1, h2, + h3, h4, h5, h6, handheld, hasOwnProperty, head, header, height, help, + hgroup, highlight, highlighttext, history, honeydew, hotpink, hr, + "hta:application", html, i, iTunes, id, identifier, iframe, img, immed, + implieds, in, inactiveborder, inactivecaption, inactivecaptiontext, + include, indent, indexOf, indianred, indigo, infobackground, infotext, + init, input, ins, isAlpha, isApplicationRunning, isArray, isDigit, + isFinite, isNaN, ivory, join, jshint, JSHINT, json, jquery, jQuery, kbd, + keygen, keys, khaki, konfabulatorVersion, label, labelled, lang, last, + lavender, lavenderblush, lawngreen, laxbreak, lbp, led, left, legend, + lemonchiffon, length, "letter-spacing", li, lib, lightblue, lightcoral, + lightcyan, lightgoldenrodyellow, lightgreen, lightpink, lightsalmon, + lightseagreen, lightskyblue, lightslategray, lightsteelblue, + lightyellow, lime, limegreen, line, "line-height", linen, link, + "list-style", "list-style-image", "list-style-position", + "list-style-type", load, loadClass, localStorage, location, log, m, magenta, + map, margin, "margin-bottom", "margin-left", "margin-right", "margin-top", + mark, "marker-offset", maroon, match, "max-height", "max-width", maxerr, + maxlen, md5, mediumaquamarine, mediumblue, mediumorchid, mediumpurple, + mediumseagreen, mediumslateblue, mediumspringgreen, mediumturquoise, + mediumvioletred, member, menu, menutext, message, meta, meter, + midnightblue, "min-height", "min-width", mintcream, mistyrose, mm, + moccasin, module, moveBy, moveTo, name, nav, navajowhite, navigator, navy, new, + newcap, noarg, node, noempty, noframes, nomen, nonew, noscript, nud, object, ol, + oldlace, olive, olivedrab, on, onbeforeunload, onblur, onerror, onevar, + onfocus, onload, onresize, onunload, opacity, open, openDatabase, openURL, opener, + opera, optgroup, option, orange, orangered, orchid, outer, outline, "outline-color", + "outline-style", "outline-width", output, overflow, "overflow-x", + "overflow-y", p, padding, "padding-bottom", "padding-left", + "padding-right", "padding-top", "page-break-after", "page-break-before", + palegoldenrod, palegreen, paleturquoise, palevioletred, papayawhip, + param, parent, parseFloat, parseInt, passfail, pc, peachpuff, peru, + pink, play, plum, plusplus, pop, popupMenu, position, powderblue, pre, + predef, preferenceGroups, preferences, print, process, progress, projection, + prompt, prototype, pt, purple, push, px, q, quit, quotes, random, range, + raw, reach, readFile, readUrl, reason, red, regexp, reloadWidget, + removeEventListener, replace, report, require, reserved, resizeBy, resizeTo, + resolvePath, resumeUpdates, respond, rhino, right, rosybrown, royalblue, + rp, rt, ruby, runCommand, runCommandInBg, saddlebrown, safe, salmon, samp, + sandybrown, saveAs, savePreferences, screen, script, scroll, scrollBy, + scrollTo, scrollbar, seagreen, seal, search, seashell, section, send, select, + serialize, setInterval, setTimeout, shift, showWidgetPreferences, + sienna, silver, skyblue, slateblue, slategray, sleep, slice, small, + snow, sort, source, span, spawn, speak, speech, split, springgreen, src, + stack, status, start, steelblue, strict, strong, style, styleproperty, sub, + substr, sum, sup, supplant, suppressUpdates, sync, system, table, + "table-layout", tan, tbody, td, teal, tellWidget, test, "text-align", + "text-decoration", "text-indent", "text-shadow", "text-transform", + textarea, tfoot, th, thead, thistle, threeddarkshadow, threedface, + threedhighlight, threedlightshadow, threedshadow, time, title, + toLowerCase, toString, toUpperCase, toint32, token, tomato, top, tr, tt, + tty, turquoise, tv, type, u, ul, undef, unescape, "unicode-bidi", + unused, unwatch, updateNow, urls, value, valueOf, var, version, + "vertical-align", video, violet, visibility, watch, WebSocket, wheat, white, + "white-space", whitesmoke, widget, width, window, windowframe, windows, + windowtext, Worker, "word-spacing", "word-wrap", yahooCheckLogin, + yahooLogin, yahooLogout, yellow, yellowgreen, "z-index" +*/ + +/*global exports: false */ + +// We build the application inside a function so that we produce only a single +// global variable. That function will be invoked immediately, and its return +// value is the JSHINT function itself. + +var JSHINT = (function () { + "use strict"; + + var adsafe_id, // The widget's ADsafe id. + adsafe_may, // The widget may load approved scripts. + adsafe_went, // ADSAFE.go has been called. + anonname, // The guessed name for anonymous functions. + approved, // ADsafe approved urls. + +// These are operators that should not be used with the ! operator. + + bang = { + '<' : true, + '<=' : true, + '==' : true, + '===': true, + '!==': true, + '!=' : true, + '>' : true, + '>=' : true, + '+' : true, + '-' : true, + '*' : true, + '/' : true, + '%' : true + }, + +// These are property names that should not be permitted in the safe subset. + + banned = { // the member names that ADsafe prohibits. + 'arguments' : true, + callee : true, + caller : true, + constructor : true, + 'eval' : true, + prototype : true, + stack : true, + unwatch : true, + valueOf : true, + watch : true + }, + + +// These are the JSHint boolean options. + + boolOptions = { + adsafe : true, // if ADsafe should be enforced + asi : true, // if automatic semicolon insertion should be tolerated + bitwise : true, // if bitwise operators should not be allowed + boss : true, // if advanced usage of assignments and == should be allowed + browser : true, // if the standard browser globals should be predefined + cap : true, // if upper case HTML should be allowed + couch : true, // if CouchDB globals should be predefined + css : true, // if CSS workarounds should be tolerated + curly : true, // if curly braces around blocks should be required (even in if/for/while) + debug : true, // if debugger statements should be allowed + devel : true, // if logging should be allowed (console, alert, etc.) + eqeqeq : true, // if === should be required + es5 : true, // if ES5 syntax should be allowed + evil : true, // if eval should be allowed + forin : true, // if for in statements must filter + fragment : true, // if HTML fragments should be allowed + immed : true, // if immediate invocations must be wrapped in parens + jquery : true, // if jQuery globals should be predefined + laxbreak : true, // if line breaks should not be checked + newcap : true, // if constructor names must be capitalized + noarg : true, // if arguments.caller and arguments.callee should be disallowed + node : true, // if the Node.js environment globals should be predefined + noempty : true, // if empty blocks should be disallowed + nonew : true, // if using `new` for side-effects should be disallowed + nomen : true, // if names should be checked + on : true, // if HTML event handlers should be allowed + onevar : true, // if only one var statement per function should be allowed + passfail : true, // if the scan should stop on first error + plusplus : true, // if increment/decrement should not be allowed + regexp : true, // if the . should not be allowed in regexp literals + rhino : true, // if the Rhino environment globals should be predefined + undef : true, // if variables should be declared before used + safe : true, // if use of some browser features should be restricted + windows : true, // if MS Windows-specigic globals should be predefined + strict : true, // require the "use strict"; pragma + sub : true, // if all forms of subscript notation are tolerated + white : true, // if strict whitespace rules apply + widget : true // if the Yahoo Widgets globals should be predefined + }, + +// browser contains a set of global names which are commonly provided by a +// web browser environment. + + browser = { + addEventListener: false, + applicationCache: false, + blur : false, + clearInterval : false, + clearTimeout : false, + close : false, + closed : false, + defaultStatus : false, + document : false, + event : false, + FileReader : false, + focus : false, + frames : false, + getComputedStyle: false, + history : false, + Image : false, + length : false, + localStorage : false, + location : false, + moveBy : false, + moveTo : false, + name : false, + navigator : false, + onbeforeunload : true, + onblur : true, + onerror : true, + onfocus : true, + onload : true, + onresize : true, + onunload : true, + open : false, + openDatabase : false, + opener : false, + Option : false, + parent : false, + print : false, + removeEventListener: false, + resizeBy : false, + resizeTo : false, + screen : false, + scroll : false, + scrollBy : false, + scrollTo : false, + setInterval : false, + setTimeout : false, + status : false, + top : false, + WebSocket : false, + window : false, + Worker : false, + XMLHttpRequest : false + }, + + couch = { + "require" : false, + respond : false, + getRow : false, + emit : false, + send : false, + start : false, + sum : false, + log : false, + exports : false, + module : false + }, + + cssAttributeData, + cssAny, + + cssColorData = { + "aliceblue" : true, + "antiquewhite" : true, + "aqua" : true, + "aquamarine" : true, + "azure" : true, + "beige" : true, + "bisque" : true, + "black" : true, + "blanchedalmond" : true, + "blue" : true, + "blueviolet" : true, + "brown" : true, + "burlywood" : true, + "cadetblue" : true, + "chartreuse" : true, + "chocolate" : true, + "coral" : true, + "cornflowerblue" : true, + "cornsilk" : true, + "crimson" : true, + "cyan" : true, + "darkblue" : true, + "darkcyan" : true, + "darkgoldenrod" : true, + "darkgray" : true, + "darkgreen" : true, + "darkkhaki" : true, + "darkmagenta" : true, + "darkolivegreen" : true, + "darkorange" : true, + "darkorchid" : true, + "darkred" : true, + "darksalmon" : true, + "darkseagreen" : true, + "darkslateblue" : true, + "darkslategray" : true, + "darkturquoise" : true, + "darkviolet" : true, + "deeppink" : true, + "deepskyblue" : true, + "dimgray" : true, + "dodgerblue" : true, + "firebrick" : true, + "floralwhite" : true, + "forestgreen" : true, + "fuchsia" : true, + "gainsboro" : true, + "ghostwhite" : true, + "gold" : true, + "goldenrod" : true, + "gray" : true, + "green" : true, + "greenyellow" : true, + "honeydew" : true, + "hotpink" : true, + "indianred" : true, + "indigo" : true, + "ivory" : true, + "khaki" : true, + "lavender" : true, + "lavenderblush" : true, + "lawngreen" : true, + "lemonchiffon" : true, + "lightblue" : true, + "lightcoral" : true, + "lightcyan" : true, + "lightgoldenrodyellow" : true, + "lightgreen" : true, + "lightpink" : true, + "lightsalmon" : true, + "lightseagreen" : true, + "lightskyblue" : true, + "lightslategray" : true, + "lightsteelblue" : true, + "lightyellow" : true, + "lime" : true, + "limegreen" : true, + "linen" : true, + "magenta" : true, + "maroon" : true, + "mediumaquamarine" : true, + "mediumblue" : true, + "mediumorchid" : true, + "mediumpurple" : true, + "mediumseagreen" : true, + "mediumslateblue" : true, + "mediumspringgreen" : true, + "mediumturquoise" : true, + "mediumvioletred" : true, + "midnightblue" : true, + "mintcream" : true, + "mistyrose" : true, + "moccasin" : true, + "navajowhite" : true, + "navy" : true, + "oldlace" : true, + "olive" : true, + "olivedrab" : true, + "orange" : true, + "orangered" : true, + "orchid" : true, + "palegoldenrod" : true, + "palegreen" : true, + "paleturquoise" : true, + "palevioletred" : true, + "papayawhip" : true, + "peachpuff" : true, + "peru" : true, + "pink" : true, + "plum" : true, + "powderblue" : true, + "purple" : true, + "red" : true, + "rosybrown" : true, + "royalblue" : true, + "saddlebrown" : true, + "salmon" : true, + "sandybrown" : true, + "seagreen" : true, + "seashell" : true, + "sienna" : true, + "silver" : true, + "skyblue" : true, + "slateblue" : true, + "slategray" : true, + "snow" : true, + "springgreen" : true, + "steelblue" : true, + "tan" : true, + "teal" : true, + "thistle" : true, + "tomato" : true, + "turquoise" : true, + "violet" : true, + "wheat" : true, + "white" : true, + "whitesmoke" : true, + "yellow" : true, + "yellowgreen" : true, + + "activeborder" : true, + "activecaption" : true, + "appworkspace" : true, + "background" : true, + "buttonface" : true, + "buttonhighlight" : true, + "buttonshadow" : true, + "buttontext" : true, + "captiontext" : true, + "graytext" : true, + "highlight" : true, + "highlighttext" : true, + "inactiveborder" : true, + "inactivecaption" : true, + "inactivecaptiontext" : true, + "infobackground" : true, + "infotext" : true, + "menu" : true, + "menutext" : true, + "scrollbar" : true, + "threeddarkshadow" : true, + "threedface" : true, + "threedhighlight" : true, + "threedlightshadow" : true, + "threedshadow" : true, + "window" : true, + "windowframe" : true, + "windowtext" : true + }, + + cssBorderStyle, + cssBreak, + + cssLengthData = { + '%': true, + 'cm': true, + 'em': true, + 'ex': true, + 'in': true, + 'mm': true, + 'pc': true, + 'pt': true, + 'px': true + }, + + cssMedia, + cssOverflow, + + devel = { + alert : false, + confirm : false, + console : false, + Debug : false, + opera : false, + prompt : false + }, + + escapes = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '/' : '\\/', + '\\': '\\\\' + }, + + funct, // The current function + + functionicity = [ + 'closure', 'exception', 'global', 'label', + 'outer', 'unused', 'var' + ], + + functions, // All of the functions + + global, // The global scope + htmltag = { + a: {}, + abbr: {}, + acronym: {}, + address: {}, + applet: {}, + area: {empty: true, parent: ' map '}, + article: {}, + aside: {}, + audio: {}, + b: {}, + base: {empty: true, parent: ' head '}, + bdo: {}, + big: {}, + blockquote: {}, + body: {parent: ' html noframes '}, + br: {empty: true}, + button: {}, + canvas: {parent: ' body p div th td '}, + caption: {parent: ' table '}, + center: {}, + cite: {}, + code: {}, + col: {empty: true, parent: ' table colgroup '}, + colgroup: {parent: ' table '}, + command: {parent: ' menu '}, + datalist: {}, + dd: {parent: ' dl '}, + del: {}, + details: {}, + dialog: {}, + dfn: {}, + dir: {}, + div: {}, + dl: {}, + dt: {parent: ' dl '}, + em: {}, + embed: {}, + fieldset: {}, + figure: {}, + font: {}, + footer: {}, + form: {}, + frame: {empty: true, parent: ' frameset '}, + frameset: {parent: ' html frameset '}, + h1: {}, + h2: {}, + h3: {}, + h4: {}, + h5: {}, + h6: {}, + head: {parent: ' html '}, + header: {}, + hgroup: {}, + hr: {empty: true}, + 'hta:application': + {empty: true, parent: ' head '}, + html: {parent: '*'}, + i: {}, + iframe: {}, + img: {empty: true}, + input: {empty: true}, + ins: {}, + kbd: {}, + keygen: {}, + label: {}, + legend: {parent: ' details fieldset figure '}, + li: {parent: ' dir menu ol ul '}, + link: {empty: true, parent: ' head '}, + map: {}, + mark: {}, + menu: {}, + meta: {empty: true, parent: ' head noframes noscript '}, + meter: {}, + nav: {}, + noframes: {parent: ' html body '}, + noscript: {parent: ' body head noframes '}, + object: {}, + ol: {}, + optgroup: {parent: ' select '}, + option: {parent: ' optgroup select '}, + output: {}, + p: {}, + param: {empty: true, parent: ' applet object '}, + pre: {}, + progress: {}, + q: {}, + rp: {}, + rt: {}, + ruby: {}, + samp: {}, + script: {empty: true, parent: ' body div frame head iframe p pre span '}, + section: {}, + select: {}, + small: {}, + span: {}, + source: {}, + strong: {}, + style: {parent: ' head ', empty: true}, + sub: {}, + sup: {}, + table: {}, + tbody: {parent: ' table '}, + td: {parent: ' tr '}, + textarea: {}, + tfoot: {parent: ' table '}, + th: {parent: ' tr '}, + thead: {parent: ' table '}, + time: {}, + title: {parent: ' head '}, + tr: {parent: ' table tbody thead tfoot '}, + tt: {}, + u: {}, + ul: {}, + 'var': {}, + video: {} + }, + + ids, // HTML ids + implied, // Implied globals + inblock, + indent, + jsonmode, + + jquery = { + '$' : false, + jQuery : false + }, + + lines, + lookahead, + member, + membersOnly, + nexttoken, + + node = { + __filename : false, + __dirname : false, + Buffer : false, + GLOBAL : false, + global : false, + module : false, + process : false, + require : false + }, + + noreach, + option, + predefined, // Global variables defined by option + prereg, + prevtoken, + + rhino = { + defineClass : false, + deserialize : false, + gc : false, + help : false, + load : false, + loadClass : false, + print : false, + quit : false, + readFile : false, + readUrl : false, + runCommand : false, + seal : false, + serialize : false, + spawn : false, + sync : false, + toint32 : false, + version : false + }, + + scope, // The current scope + src, + stack, + +// standard contains the global names that are provided by the +// ECMAScript standard. + + standard = { + Array : false, + Boolean : false, + Date : false, + decodeURI : false, + decodeURIComponent : false, + encodeURI : false, + encodeURIComponent : false, + Error : false, + 'eval' : false, + EvalError : false, + Function : false, + hasOwnProperty : false, + isFinite : false, + isNaN : false, + JSON : false, + Math : false, + Number : false, + Object : false, + parseInt : false, + parseFloat : false, + RangeError : false, + ReferenceError : false, + RegExp : false, + String : false, + SyntaxError : false, + TypeError : false, + URIError : false + }, + + standard_member = { + E : true, + LN2 : true, + LN10 : true, + LOG2E : true, + LOG10E : true, + MAX_VALUE : true, + MIN_VALUE : true, + NEGATIVE_INFINITY : true, + PI : true, + POSITIVE_INFINITY : true, + SQRT1_2 : true, + SQRT2 : true + }, + + strict_mode, + syntax = {}, + tab, + token, + urls, + warnings, + +// widget contains the global names which are provided to a Yahoo +// (fna Konfabulator) widget. + + widget = { + alert : true, + animator : true, + appleScript : true, + beep : true, + bytesToUIString : true, + Canvas : true, + chooseColor : true, + chooseFile : true, + chooseFolder : true, + closeWidget : true, + COM : true, + convertPathToHFS : true, + convertPathToPlatform : true, + CustomAnimation : true, + escape : true, + FadeAnimation : true, + filesystem : true, + Flash : true, + focusWidget : true, + form : true, + FormField : true, + Frame : true, + HotKey : true, + Image : true, + include : true, + isApplicationRunning : true, + iTunes : true, + konfabulatorVersion : true, + log : true, + md5 : true, + MenuItem : true, + MoveAnimation : true, + openURL : true, + play : true, + Point : true, + popupMenu : true, + preferenceGroups : true, + preferences : true, + print : true, + prompt : true, + random : true, + Rectangle : true, + reloadWidget : true, + ResizeAnimation : true, + resolvePath : true, + resumeUpdates : true, + RotateAnimation : true, + runCommand : true, + runCommandInBg : true, + saveAs : true, + savePreferences : true, + screen : true, + ScrollBar : true, + showWidgetPreferences : true, + sleep : true, + speak : true, + Style : true, + suppressUpdates : true, + system : true, + tellWidget : true, + Text : true, + TextArea : true, + Timer : true, + unescape : true, + updateNow : true, + URL : true, + Web : true, + widget : true, + Window : true, + XMLDOM : true, + XMLHttpRequest : true, + yahooCheckLogin : true, + yahooLogin : true, + yahooLogout : true + }, + + windows = { + ActiveXObject: false, + CScript : false, + Debug : false, + Enumerator : false, + System : false, + VBArray : false, + WScript : false + }, + +// xmode is used to adapt to the exceptions in html parsing. +// It can have these states: +// false .js script file +// html +// outer +// script +// style +// scriptstring +// styleproperty + + xmode, + xquote, + +// Regular expressions. Some of these are stupidly long. + +// unsafe comment or string + ax = /@cc|<\/?|script|\]\s*\]|<\s*!|</i, +// unsafe characters that are silently deleted by one or more browsers + cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/, +// token + tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jshint|members?|global)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/, +// html token + hx = /^\s*(['"=>\/&#]|<(?:\/|\!(?:--)?)?|[a-zA-Z][a-zA-Z0-9_\-:]*|[0-9]+|--)/, +// characters in strings that need escapement + nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/, + nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, +// outer html token + ox = /[>&]|<[\/!]?|--/, +// star slash + lx = /\*\/|\/\*/, +// identifier + ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/, +// javascript url + jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i, +// url badness + ux = /&|\+|\u00AD|\.\.|\/\*|%[^;]|base64|url|expression|data|mailto/i, +// style + sx = /^\s*([{:#%.=,>+\[\]@()"';]|\*=?|\$=|\|=|\^=|~=|[a-zA-Z_][a-zA-Z0-9_\-]*|[0-9]+|<\/|\/\*)/, + ssx = /^\s*([@#!"'};:\-%.=,+\[\]()*_]|[a-zA-Z][a-zA-Z0-9._\-]*|\/\*?|\d+(?:\.\d+)?|<\/)/, +// attributes characters + qx = /[^a-zA-Z0-9+\-_\/ ]/, +// query characters for ids + dx = /[\[\]\/\\"'*<>.&:(){}+=#]/, +// catches /* falls through */ comments + ft = /^\s*\/\*\s*falls\sthrough\s*\*\/\s*$/, + + rx = { + outer: hx, + html: hx, + style: sx, + styleproperty: ssx + }; + + + function F() {} // Used by Object.create + + function is_own(object, name) { + +// The object.hasOwnProperty method fails when the property under consideration +// is named 'hasOwnProperty'. So we have to use this more convoluted form. + + return Object.prototype.hasOwnProperty.call(object, name); + } + +// Provide critical ES5 functions to ES3. + + if (typeof Array.isArray !== 'function') { + Array.isArray = function (o) { + return Object.prototype.toString.apply(o) === '[object Array]'; + }; + } + + if (typeof Object.create !== 'function') { + Object.create = function (o) { + F.prototype = o; + return new F(); + }; + } + + if (typeof Object.keys !== 'function') { + Object.keys = function (o) { + var a = [], k; + for (k in o) { + if (is_own(o, k)) { + a.push(k); + } + } + return a; + }; + } + +// Non standard methods + + if (typeof String.prototype.entityify !== 'function') { + String.prototype.entityify = function () { + return this + .replace(/&/g, '&') + .replace(//g, '>'); + }; + } + + if (typeof String.prototype.isAlpha !== 'function') { + String.prototype.isAlpha = function () { + return (this >= 'a' && this <= 'z\uffff') || + (this >= 'A' && this <= 'Z\uffff'); + }; + } + + if (typeof String.prototype.isDigit !== 'function') { + String.prototype.isDigit = function () { + return (this >= '0' && this <= '9'); + }; + } + + if (typeof String.prototype.supplant !== 'function') { + String.prototype.supplant = function (o) { + return this.replace(/\{([^{}]*)\}/g, function (a, b) { + var r = o[b]; + return typeof r === 'string' || typeof r === 'number' ? r : a; + }); + }; + } + + if (typeof String.prototype.name !== 'function') { + String.prototype.name = function () { + +// If the string looks like an identifier, then we can return it as is. +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can simply slap some quotes around it. +// Otherwise we must also replace the offending characters with safe +// sequences. + + if (ix.test(this)) { + return this; + } + if (nx.test(this)) { + return '"' + this.replace(nxg, function (a) { + var c = escapes[a]; + if (c) { + return c; + } + return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4); + }) + '"'; + } + return '"' + this + '"'; + }; + } + + + function combine(t, o) { + var n; + for (n in o) { + if (is_own(o, n)) { + t[n] = o[n]; + } + } + } + + function assume() { + if (option.safe) + return; + + if (option.couch) + combine(predefined, couch); + + if (option.rhino) + combine(predefined, rhino); + + if (option.node) + combine(predefined, node); + + if (option.devel) + combine(predefined, devel); + + if (option.browser) + combine(predefined, browser); + + if (option.jquery) + combine(predefined, jquery); + + if (option.windows) + combine(predefined, windows); + + if (option.widget) + combine(predefined, widget); + } + + +// Produce an error warning. + + function quit(m, l, ch) { + throw { + name: 'JSHintError', + line: l, + character: ch, + message: m + " (" + Math.floor((l / lines.length) * 100) + + "% scanned)." + }; + } + + function warning(m, t, a, b, c, d) { + var ch, l, w; + t = t || nexttoken; + if (t.id === '(end)') { // `~ + t = token; + } + l = t.line || 0; + ch = t.from || 0; + w = { + id: '(error)', + raw: m, + evidence: lines[l - 1] || '', + line: l, + character: ch, + a: a, + b: b, + c: c, + d: d + }; + w.reason = m.supplant(w); + JSHINT.errors.push(w); + if (option.passfail) { + quit('Stopping. ', l, ch); + } + warnings += 1; + if (warnings >= option.maxerr) { + quit("Too many errors.", l, ch); + } + return w; + } + + function warningAt(m, l, ch, a, b, c, d) { + return warning(m, { + line: l, + from: ch + }, a, b, c, d); + } + + function error(m, t, a, b, c, d) { + var w = warning(m, t, a, b, c, d); + quit("Stopping, unable to continue.", w.line, w.character); + } + + function errorAt(m, l, ch, a, b, c, d) { + return error(m, { + line: l, + from: ch + }, a, b, c, d); + } + + + +// lexical analysis and token construction + + var lex = (function lex() { + var character, from, line, s; + +// Private lex methods + + function nextLine() { + var at; + if (line >= lines.length) { + return false; + } + character = 1; + s = lines[line]; + line += 1; + at = s.search(/ \t/); + if (at >= 0) { + warningAt("Mixed spaces and tabs.", line, at + 1); + } + s = s.replace(/\t/g, tab); + at = s.search(cx); + if (at >= 0) { + warningAt("Unsafe character.", line, at); + } + if (option.maxlen && option.maxlen < s.length) { + warningAt("Line too long.", line, s.length); + } + return true; + } + +// Produce a token object. The token inherits from a syntax symbol. + + function it(type, value) { + var i, t; + if (type === '(color)' || type === '(range)') { + t = {type: type}; + } else if (type === '(punctuator)' || + (type === '(identifier)' && is_own(syntax, value))) { + t = syntax[value] || syntax['(error)']; + } else { + t = syntax[type]; + } + t = Object.create(t); + if (type === '(string)' || type === '(range)') { + if (jx.test(value)) { + warningAt("Script URL.", line, from); + } + } + if (type === '(identifier)') { + t.identifier = true; + if (value === '__iterator__' || value === '__proto__') { + errorAt("Reserved name '{a}'.", + line, from, value); + } else if (option.nomen && + (value.charAt(0) === '_' || + value.charAt(value.length - 1) === '_')) { + warningAt("Unexpected {a} in '{b}'.", line, from, + "dangling '_'", value); + } + } + t.value = value; + t.line = line; + t.character = character; + t.from = from; + i = t.id; + if (i !== '(endline)') { + prereg = i && + (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) || + i === 'return'); + } + return t; + } + +// Public lex methods + + return { + init: function (source) { + if (typeof source === 'string') { + lines = source + .replace(/\r\n/g, '\n') + .replace(/\r/g, '\n') + .split('\n'); + } else { + lines = source; + } + line = 0; + nextLine(); + from = 1; + }, + + range: function (begin, end) { + var c, value = ''; + from = character; + if (s.charAt(0) !== begin) { + errorAt("Expected '{a}' and instead saw '{b}'.", + line, character, begin, s.charAt(0)); + } + for (;;) { + s = s.slice(1); + character += 1; + c = s.charAt(0); + switch (c) { + case '': + errorAt("Missing '{a}'.", line, character, c); + break; + case end: + s = s.slice(1); + character += 1; + return it('(range)', value); + case xquote: + case '\\': + warningAt("Unexpected '{a}'.", line, character, c); + } + value += c; + } + + }, + +// token -- this is called by advance to get the next token. + + token: function () { + var b, c, captures, d, depth, high, i, l, low, q, t; + + function match(x) { + var r = x.exec(s), r1; + if (r) { + l = r[0].length; + r1 = r[1]; + c = r1.charAt(0); + s = s.substr(l); + from = character + l - r1.length; + character += l; + return r1; + } + } + + function string(x) { + var c, j, r = ''; + + if (jsonmode && x !== '"') { + warningAt("Strings must use doublequote.", + line, character); + } + + if (xquote === x || (xmode === 'scriptstring' && !xquote)) { + return it('(punctuator)', x); + } + + function esc(n) { + var i = parseInt(s.substr(j + 1, n), 16); + j += n; + if (i >= 32 && i <= 126 && + i !== 34 && i !== 92 && i !== 39) { + warningAt("Unnecessary escapement.", line, character); + } + character += n; + c = String.fromCharCode(i); + } + j = 0; + for (;;) { + while (j >= s.length) { + j = 0; + if (xmode !== 'html' || !nextLine()) { + errorAt("Unclosed string.", line, from); + } + } + c = s.charAt(j); + if (c === x) { + character += 1; + s = s.substr(j + 1); + return it('(string)', r, x); + } + if (c < ' ') { + if (c === '\n' || c === '\r') { + break; + } + warningAt("Control character in string: {a}.", + line, character + j, s.slice(0, j)); + } else if (c === xquote) { + warningAt("Bad HTML string", line, character + j); + } else if (c === '<') { + if (option.safe && xmode === 'html') { + warningAt("ADsafe string violation.", + line, character + j); + } else if (s.charAt(j + 1) === '/' && (xmode || option.safe)) { + warningAt("Expected '<\\/' and instead saw ' 0) { + character += 1; + s = s.slice(i); + break; + } else { + if (!nextLine()) { + return it('(end)', ''); + } + } + } + t = match(rx[xmode] || tx); + if (!t) { + t = ''; + c = ''; + while (s && s < '!') { + s = s.substr(1); + } + if (s) { + if (xmode === 'html') { + return it('(error)', s.charAt(0)); + } else { + errorAt("Unexpected '{a}'.", + line, character, s.substr(0, 1)); + } + } + } else { + + // identifier + + if (c.isAlpha() || c === '_' || c === '$') { + return it('(identifier)', t); + } + + // number + + if (c.isDigit()) { + if (xmode !== 'style' && !isFinite(Number(t))) { + warningAt("Bad number '{a}'.", + line, character, t); + } + if (xmode !== 'style' && + xmode !== 'styleproperty' && + s.substr(0, 1).isAlpha()) { + warningAt("Missing space after '{a}'.", + line, character, t); + } + if (c === '0') { + d = t.substr(1, 1); + if (d.isDigit()) { + if (token.id !== '.' && xmode !== 'styleproperty') { + warningAt("Don't use extra leading zeros '{a}'.", + line, character, t); + } + } else if (jsonmode && (d === 'x' || d === 'X')) { + warningAt("Avoid 0x-. '{a}'.", + line, character, t); + } + } + if (t.substr(t.length - 1) === '.') { + warningAt( +"A trailing decimal point can be confused with a dot '{a}'.", line, character, t); + } + return it('(number)', t); + } + switch (t) { + + // string + + case '"': + case "'": + return string(t); + + // // comment + + case '//': + if (src || (xmode && xmode !== 'script')) { + warningAt("Unexpected comment.", line, character); + } else if (xmode === 'script' && /<\s*\//i.test(s)) { + warningAt("Unexpected <\/ in comment.", line, character); + } else if ((option.safe || xmode === 'script') && ax.test(s)) { + warningAt("Dangerous comment.", line, character); + } + s = ''; + token.comment = true; + break; + + // /* comment + + case '/*': + if (src || (xmode && xmode !== 'script' && xmode !== 'style' && xmode !== 'styleproperty')) { + warningAt("Unexpected comment.", line, character); + } + if (option.safe && ax.test(s)) { + warningAt("ADsafe comment violation.", line, character); + } + for (;;) { + i = s.search(lx); + if (i >= 0) { + break; + } + if (!nextLine()) { + errorAt("Unclosed comment.", line, character); + } else { + if (option.safe && ax.test(s)) { + warningAt("ADsafe comment violation.", + line, character); + } + } + } + character += i + 2; + if (s.substr(i, 1) === '/') { + errorAt("Nested comment.", line, character); + } + s = s.substr(i + 2); + token.comment = true; + break; + + // /*members /*jshint /*global + + case '/*members': + case '/*member': + case '/*jshint': + case '/*global': + case '*/': + return { + value: t, + type: 'special', + line: line, + character: character, + from: from + }; + + case '': + break; + // / + case '/': + if (token.id === '/=') { + errorAt( +"A regular expression literal can be confused with '/='.", line, from); + } + if (prereg) { + depth = 0; + captures = 0; + l = 0; + for (;;) { + b = true; + c = s.charAt(l); + l += 1; + switch (c) { + case '': + errorAt("Unclosed regular expression.", + line, from); + return; + case '/': + if (depth > 0) { + warningAt("Unescaped '{a}'.", + line, from + l, '/'); + } + c = s.substr(0, l - 1); + q = { + g: true, + i: true, + m: true + }; + while (q[s.charAt(l)] === true) { + q[s.charAt(l)] = false; + l += 1; + } + character += l; + s = s.substr(l); + q = s.charAt(0); + if (q === '/' || q === '*') { + errorAt("Confusing regular expression.", + line, from); + } + return it('(regexp)', c); + case '\\': + c = s.charAt(l); + if (c < ' ') { + warningAt( +"Unexpected control character in regular expression.", line, from + l); + } else if (c === '<') { + warningAt( +"Unexpected escaped character '{a}' in regular expression.", line, from + l, c); + } + l += 1; + break; + case '(': + depth += 1; + b = false; + if (s.charAt(l) === '?') { + l += 1; + switch (s.charAt(l)) { + case ':': + case '=': + case '!': + l += 1; + break; + default: + warningAt( +"Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l)); + } + } else { + captures += 1; + } + break; + case '|': + b = false; + break; + case ')': + if (depth === 0) { + warningAt("Unescaped '{a}'.", + line, from + l, ')'); + } else { + depth -= 1; + } + break; + case ' ': + q = 1; + while (s.charAt(l) === ' ') { + l += 1; + q += 1; + } + if (q > 1) { + warningAt( +"Spaces are hard to count. Use {{a}}.", line, from + l, q); + } + break; + case '[': + c = s.charAt(l); + if (c === '^') { + l += 1; + if (option.regexp) { + warningAt("Insecure '{a}'.", + line, from + l, c); + } else if (s.charAt(l) === ']') { + errorAt("Unescaped '{a}'.", + line, from + l, '^'); + } + } + q = false; + if (c === ']') { + warningAt("Empty class.", line, + from + l - 1); + q = true; + } +klass: do { + c = s.charAt(l); + l += 1; + switch (c) { + case '[': + case '^': + warningAt("Unescaped '{a}'.", + line, from + l, c); + q = true; + break; + case '-': + if (q) { + q = false; + } else { + warningAt("Unescaped '{a}'.", + line, from + l, '-'); + q = true; + } + break; + case ']': + if (!q) { + warningAt("Unescaped '{a}'.", + line, from + l - 1, '-'); + } + break klass; + case '\\': + c = s.charAt(l); + if (c < ' ') { + warningAt( +"Unexpected control character in regular expression.", line, from + l); + } else if (c === '<') { + warningAt( +"Unexpected escaped character '{a}' in regular expression.", line, from + l, c); + } + l += 1; + q = true; + break; + case '/': + warningAt("Unescaped '{a}'.", + line, from + l - 1, '/'); + q = true; + break; + case '<': + if (xmode === 'script') { + c = s.charAt(l); + if (c === '!' || c === '/') { + warningAt( +"HTML confusion in regular expression '<{a}'.", line, from + l, c); + } + } + q = true; + break; + default: + q = true; + } + } while (c); + break; + case '.': + if (option.regexp) { + warningAt("Insecure '{a}'.", line, + from + l, c); + } + break; + case ']': + case '?': + case '{': + case '}': + case '+': + case '*': + warningAt("Unescaped '{a}'.", line, + from + l, c); + break; + case '<': + if (xmode === 'script') { + c = s.charAt(l); + if (c === '!' || c === '/') { + warningAt( +"HTML confusion in regular expression '<{a}'.", line, from + l, c); + } + } + } + if (b) { + switch (s.charAt(l)) { + case '?': + case '+': + case '*': + l += 1; + if (s.charAt(l) === '?') { + l += 1; + } + break; + case '{': + l += 1; + c = s.charAt(l); + if (c < '0' || c > '9') { + warningAt( +"Expected a number and instead saw '{a}'.", line, from + l, c); + } + l += 1; + low = +c; + for (;;) { + c = s.charAt(l); + if (c < '0' || c > '9') { + break; + } + l += 1; + low = +c + (low * 10); + } + high = low; + if (c === ',') { + l += 1; + high = Infinity; + c = s.charAt(l); + if (c >= '0' && c <= '9') { + l += 1; + high = +c; + for (;;) { + c = s.charAt(l); + if (c < '0' || c > '9') { + break; + } + l += 1; + high = +c + (high * 10); + } + } + } + if (s.charAt(l) !== '}') { + warningAt( +"Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c); + } else { + l += 1; + } + if (s.charAt(l) === '?') { + l += 1; + } + if (low > high) { + warningAt( +"'{a}' should not be greater than '{b}'.", line, from + l, low, high); + } + } + } + } + c = s.substr(0, l - 1); + character += l; + s = s.substr(l); + return it('(regexp)', c); + } + return it('(punctuator)', t); + + // punctuator + + case '.", line, character); + } + character += 3; + s = s.slice(i + 3); + break; + case '#': + if (xmode === 'html' || xmode === 'styleproperty') { + for (;;) { + c = s.charAt(0); + if ((c < '0' || c > '9') && + (c < 'a' || c > 'f') && + (c < 'A' || c > 'F')) { + break; + } + character += 1; + s = s.substr(1); + t += c; + } + if (t.length !== 4 && t.length !== 7) { + warningAt("Bad hex color '{a}'.", line, + from + l, t); + } + return it('(color)', t); + } + return it('(punctuator)', t); + default: + if (xmode === 'outer' && c === '&') { + character += 1; + s = s.substr(1); + for (;;) { + c = s.charAt(0); + character += 1; + s = s.substr(1); + if (c === ';') { + break; + } + if (!((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + c === '#')) { + errorAt("Bad entity", line, from + l, + character); + } + } + break; + } + return it('(punctuator)', t); + } + } + } + } + }; + }()); + + + function addlabel(t, type) { + + if (option.safe && funct['(global)'] && + typeof predefined[t] !== 'boolean') { + warning('ADsafe global: ' + t + '.', token); + } else if (t === 'hasOwnProperty') { + warning("'hasOwnProperty' is a really bad name."); + } + +// Define t in the current function in the current scope. + + if (is_own(funct, t) && !funct['(global)']) { + warning(funct[t] === true ? + "'{a}' was used before it was defined." : + "'{a}' is already defined.", + nexttoken, t); + } + funct[t] = type; + if (funct['(global)']) { + global[t] = funct; + if (is_own(implied, t)) { + warning("'{a}' was used before it was defined.", nexttoken, t); + delete implied[t]; + } + } else { + scope[t] = funct; + } + } + + + function doOption() { + var b, obj, filter, o = nexttoken.value, t, v; + switch (o) { + case '*/': + error("Unbegun comment."); + break; + case '/*members': + case '/*member': + o = '/*members'; + if (!membersOnly) { + membersOnly = {}; + } + obj = membersOnly; + break; + case '/*jshint': + if (option.safe) { + warning("ADsafe restriction."); + } + obj = option; + filter = boolOptions; + break; + case '/*global': + if (option.safe) { + warning("ADsafe restriction."); + } + obj = predefined; + break; + default: + error("What?"); + } + t = lex.token(); +loop: for (;;) { + for (;;) { + if (t.type === 'special' && t.value === '*/') { + break loop; + } + if (t.id !== '(endline)' && t.id !== ',') { + break; + } + t = lex.token(); + } + if (t.type !== '(string)' && t.type !== '(identifier)' && + o !== '/*members') { + error("Bad option.", t); + } + v = lex.token(); + if (v.id === ':') { + v = lex.token(); + if (obj === membersOnly) { + error("Expected '{a}' and instead saw '{b}'.", + t, '*/', ':'); + } + if (t.value === 'indent' && o === '/*jshint') { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.white = true; + obj.indent = b; + } else if (t.value === 'maxerr' && o === '/*jshint') { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.maxerr = b; + } else if (t.value === 'maxlen' && o === '/*jshint') { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.maxlen = b; + } else if (v.value === 'true') { + obj[t.value] = true; + } else if (v.value === 'false') { + obj[t.value] = false; + } else { + error("Bad option value.", v); + } + t = lex.token(); + } else { + if (o === '/*jshint') { + error("Missing option value.", t); + } + obj[t.value] = false; + t = v; + } + } + if (filter) { + assume(); + } + } + + +// We need a peek function. If it has an argument, it peeks that much farther +// ahead. It is used to distinguish +// for ( var i in ... +// from +// for ( var i = ... + + function peek(p) { + var i = p || 0, j = 0, t; + + while (j <= i) { + t = lookahead[j]; + if (!t) { + t = lookahead[j] = lex.token(); + } + j += 1; + } + return t; + } + + + +// Produce the next token. It looks for programming errors. + + function advance(id, t) { + switch (token.id) { + case '(number)': + if (nexttoken.id === '.') { + warning( +"A dot following a number can be confused with a decimal point.", token); + } + break; + case '-': + if (nexttoken.id === '-' || nexttoken.id === '--') { + warning("Confusing minusses."); + } + break; + case '+': + if (nexttoken.id === '+' || nexttoken.id === '++') { + warning("Confusing plusses."); + } + break; + } + if (token.type === '(string)' || token.identifier) { + anonname = token.value; + } + + if (id && nexttoken.id !== id) { + if (t) { + if (nexttoken.id === '(end)') { + warning("Unmatched '{a}'.", t, t.id); + } else { + warning( +"Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.", + nexttoken, id, t.id, t.line, nexttoken.value); + } + } else if (nexttoken.type !== '(identifier)' || + nexttoken.value !== id) { + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, id, nexttoken.value); + } + } + prevtoken = token; + token = nexttoken; + for (;;) { + nexttoken = lookahead.shift() || lex.token(); + if (nexttoken.id === '(end)' || nexttoken.id === '(error)') { + return; + } + if (nexttoken.type === 'special') { + doOption(); + } else { + if (nexttoken.id !== '(endline)') { + break; + } + } + } + } + + +// This is the heart of JSHINT, the Pratt parser. In addition to parsing, it +// is looking for ad hoc lint patterns. We add .fud to Pratt's model, which is +// like .nud except that it is only used on the first token of a statement. +// Having .fud makes it much easier to define statement-oriented languages like +// JavaScript. I retained Pratt's nomenclature. + +// .nud Null denotation +// .fud First null denotation +// .led Left denotation +// lbp Left binding power +// rbp Right binding power + +// They are elements of the parsing method called Top Down Operator Precedence. + + function expression(rbp, initial) { + var left; + if (nexttoken.id === '(end)') { + error("Unexpected early end of program.", token); + } + advance(); + if (option.safe && typeof predefined[token.value] === 'boolean' && + (nexttoken.id !== '(' && nexttoken.id !== '.')) { + warning('ADsafe violation.', token); + } + if (initial) { + anonname = 'anonymous'; + funct['(verb)'] = token.value; + } + if (initial === true && token.fud) { + left = token.fud(); + } else { + if (token.nud) { + left = token.nud(); + } else { + if (nexttoken.type === '(number)' && token.id === '.') { + warning( +"A leading decimal point can be confused with a dot: '.{a}'.", + token, nexttoken.value); + advance(); + return token; + } else { + error("Expected an identifier and instead saw '{a}'.", + token, token.id); + } + } + while (rbp < nexttoken.lbp) { + advance(); + if (token.led) { + left = token.led(left); + } else { + error("Expected an operator and instead saw '{a}'.", + token, token.id); + } + } + } + return left; + } + + +// Functions for conformance of style. + + function adjacent(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white || xmode === 'styleproperty' || xmode === 'style') { + if (left.character !== right.from && left.line === right.line) { + warning("Unexpected space after '{a}'.", right, left.value); + } + } + } + + function nobreak(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white && (left.character !== right.from || left.line !== right.line)) { + warning("Unexpected space before '{a}'.", right, right.value); + } + } + + function nospace(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white && !left.comment) { + if (left.line === right.line) { + adjacent(left, right); + } + } + } + + function nonadjacent(left, right) { + if (option.white) { + left = left || token; + right = right || nexttoken; + if (left.line === right.line && left.character === right.from) { + warning("Missing space after '{a}'.", + nexttoken, left.value); + } + } + } + + function nobreaknonadjacent(left, right) { + left = left || token; + right = right || nexttoken; + if (!option.laxbreak && left.line !== right.line) { + warning("Bad line breaking before '{a}'.", right, right.id); + } else if (option.white) { + left = left || token; + right = right || nexttoken; + if (left.character === right.from) { + warning("Missing space after '{a}'.", + nexttoken, left.value); + } + } + } + + function indentation(bias) { + var i; + if (option.white && nexttoken.id !== '(end)') { + i = indent + (bias || 0); + if (nexttoken.from !== i) { + warning( +"Expected '{a}' to have an indentation at {b} instead at {c}.", + nexttoken, nexttoken.value, i, nexttoken.from); + } + } + } + + function nolinebreak(t) { + t = t || token; + if (t.line !== nexttoken.line) { + warning("Line breaking error '{a}'.", t, t.value); + } + } + + + function comma() { + if (token.line !== nexttoken.line) { + if (!option.laxbreak) { + warning("Bad line breaking before '{a}'.", token, nexttoken.id); + } + } else if (token.character !== nexttoken.from && option.white) { + warning("Unexpected space after '{a}'.", nexttoken, token.value); + } + advance(','); + nonadjacent(token, nexttoken); + } + + +// Functional constructors for making the symbols that will be inherited by +// tokens. + + function symbol(s, p) { + var x = syntax[s]; + if (!x || typeof x !== 'object') { + syntax[s] = x = { + id: s, + lbp: p, + value: s + }; + } + return x; + } + + + function delim(s) { + return symbol(s, 0); + } + + + function stmt(s, f) { + var x = delim(s); + x.identifier = x.reserved = true; + x.fud = f; + return x; + } + + + function blockstmt(s, f) { + var x = stmt(s, f); + x.block = true; + return x; + } + + + function reserveName(x) { + var c = x.id.charAt(0); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + x.identifier = x.reserved = true; + } + return x; + } + + + function prefix(s, f) { + var x = symbol(s, 150); + reserveName(x); + x.nud = (typeof f === 'function') ? f : function () { + this.right = expression(150); + this.arity = 'unary'; + if (this.id === '++' || this.id === '--') { + if (option.plusplus) { + warning("Unexpected use of '{a}'.", this, this.id); + } else if ((!this.right.identifier || this.right.reserved) && + this.right.id !== '.' && this.right.id !== '[') { + warning("Bad operand.", this); + } + } + return this; + }; + return x; + } + + + function type(s, f) { + var x = delim(s); + x.type = s; + x.nud = f; + return x; + } + + + function reserve(s, f) { + var x = type(s, f); + x.identifier = x.reserved = true; + return x; + } + + + function reservevar(s, v) { + return reserve(s, function () { + if (typeof v === 'function') { + v(this); + } + return this; + }); + } + + + function infix(s, f, p, w) { + var x = symbol(s, p); + reserveName(x); + x.led = function (left) { + if (!w) { + nobreaknonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + } + if (typeof f === 'function') { + return f(left, this); + } else { + this.left = left; + this.right = expression(p); + return this; + } + }; + return x; + } + + + function relation(s, f) { + var x = symbol(s, 100); + x.led = function (left) { + nobreaknonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + var right = expression(100); + if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) { + warning("Use the isNaN function to compare with NaN.", this); + } else if (f) { + f.apply(this, [left, right]); + } + if (left.id === '!') { + warning("Confusing use of '{a}'.", left, '!'); + } + if (right.id === '!') { + warning("Confusing use of '{a}'.", left, '!'); + } + this.left = left; + this.right = right; + return this; + }; + return x; + } + + + function isPoorRelation(node) { + return node && + ((node.type === '(number)' && +node.value === 0) || + (node.type === '(string)' && node.value === '') || + (node.type === 'null' && !option.boss) || + node.type === 'true' || + node.type === 'false' || + node.type === 'undefined'); + } + + + function assignop(s, f) { + symbol(s, 20).exps = true; + return infix(s, function (left, that) { + var l; + that.left = left; + if (predefined[left.value] === false && + scope[left.value]['(global)'] === true) { + warning("Read only.", left); + } else if (left['function']) { + warning("'{a}' is a function.", left, left.value); + } + if (option.safe) { + l = left; + do { + if (typeof predefined[l.value] === 'boolean') { + warning('ADsafe violation.', l); + } + l = l.left; + } while (l); + } + if (left) { + if (left.id === '.' || left.id === '[') { + if (!left.left || left.left.value === 'arguments') { + warning('Bad assignment.', that); + } + that.right = expression(19); + return that; + } else if (left.identifier && !left.reserved) { + if (funct[left.value] === 'exception') { + warning("Do not assign to the exception parameter.", left); + } + that.right = expression(19); + return that; + } + if (left === syntax['function']) { + warning( +"Expected an identifier in an assignment and instead saw a function invocation.", + token); + } + } + error("Bad assignment.", that); + }, 20); + } + + + function bitwise(s, f, p) { + var x = symbol(s, p); + reserveName(x); + x.led = (typeof f === 'function') ? f : function (left) { + if (option.bitwise) { + warning("Unexpected use of '{a}'.", this, this.id); + } + this.left = left; + this.right = expression(p); + return this; + }; + return x; + } + + + function bitwiseassignop(s) { + symbol(s, 20).exps = true; + return infix(s, function (left, that) { + if (option.bitwise) { + warning("Unexpected use of '{a}'.", that, that.id); + } + nonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + if (left) { + if (left.id === '.' || left.id === '[' || + (left.identifier && !left.reserved)) { + expression(19); + return that; + } + if (left === syntax['function']) { + warning( +"Expected an identifier in an assignment, and instead saw a function invocation.", + token); + } + return that; + } + error("Bad assignment.", that); + }, 20); + } + + + function suffix(s, f) { + var x = symbol(s, 150); + x.led = function (left) { + if (option.plusplus) { + warning("Unexpected use of '{a}'.", this, this.id); + } else if ((!left.identifier || left.reserved) && + left.id !== '.' && left.id !== '[') { + warning("Bad operand.", this); + } + this.left = left; + return this; + }; + return x; + } + + + // fnparam means that this identifier is being defined as a function + // argument (see identifier()) + function optionalidentifier(fnparam) { + if (nexttoken.identifier) { + advance(); + if (option.safe && banned[token.value]) { + warning("ADsafe violation: '{a}'.", token, token.value); + } else if (token.reserved && !option.es5) { + // `undefined` as a function param is a common pattern to protect + // against the case when somebody does `undefined = true` and + // help with minification. More info: https://gist.github.com/315916 + if (!fnparam || token.value != 'undefined') { + warning("Expected an identifier and instead saw '{a}' (a reserved word).", + token, token.id); + } + } + return token.value; + } + } + + // fnparam means that this identifier is being defined as a function + // argument + function identifier(fnparam) { + var i = optionalidentifier(fnparam); + if (i) { + return i; + } + if (token.id === 'function' && nexttoken.id === '(') { + warning("Missing name in function statement."); + } else { + error("Expected an identifier and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + } + + + function reachable(s) { + var i = 0, t; + if (nexttoken.id !== ';' || noreach) { + return; + } + for (;;) { + t = peek(i); + if (t.reach) { + return; + } + if (t.id !== '(endline)') { + if (t.id === 'function') { + warning( +"Inner functions should be listed at the top of the outer function.", t); + break; + } + warning("Unreachable '{a}' after '{b}'.", t, t.value, s); + break; + } + i += 1; + } + } + + + function statement(noindent) { + var i = indent, r, s = scope, t = nexttoken; + +// We don't like the empty statement. + + if (t.id === ';') { + warning("Unnecessary semicolon.", t); + advance(';'); + return; + } + +// Is this a labelled statement? + + if (t.identifier && !t.reserved && peek().id === ':') { + advance(); + advance(':'); + scope = Object.create(s); + addlabel(t.value, 'label'); + if (!nexttoken.labelled) { + warning("Label '{a}' on {b} statement.", + nexttoken, t.value, nexttoken.value); + } + if (jx.test(t.value + ':')) { + warning("Label '{a}' looks like a javascript url.", + t, t.value); + } + nexttoken.label = t.value; + t = nexttoken; + } + +// Parse the statement. + + if (!noindent) { + indentation(); + } + r = expression(0, true); + +// Look for the final semicolon. + + if (!t.block) { + if (!r || !r.exps) { + warning("Expected an assignment or function call and instead saw an expression.", token); + } else if (option.nonew && r.id === '(' && r.left.id === 'new') { + warning("Do not use 'new' for side effects."); + } + if (nexttoken.id !== ';') { + if (!option.asi) { + warningAt("Missing semicolon.", token.line, token.from + token.value.length); + } + } else { + adjacent(token, nexttoken); + advance(';'); + nonadjacent(token, nexttoken); + } + } + +// Restore the indentation. + + indent = i; + scope = s; + return r; + } + + + function use_strict() { + if (nexttoken.value === 'use strict') { + if (strict_mode) { + warning("Unnecessary \"use strict\"."); + } + advance(); + advance(';'); + strict_mode = true; + option.newcap = true; + option.undef = true; + return true; + } else { + return false; + } + } + + + function statements(begin) { + var a = [], f, p; + if (option.adsafe) { + switch (begin) { + case 'script': + +// JSHint is also the static analizer for ADsafe. See www.ADsafe.org. + + if (!adsafe_may) { + if (nexttoken.value !== 'ADSAFE' || + peek(0).id !== '.' || + (peek(1).value !== 'id' && + peek(1).value !== 'go')) { + error('ADsafe violation: Missing ADSAFE.id or ADSAFE.go.', + nexttoken); + } + } + if (nexttoken.value === 'ADSAFE' && + peek(0).id === '.' && + peek(1).value === 'id') { + if (adsafe_may) { + error('ADsafe violation.', nexttoken); + } + advance('ADSAFE'); + advance('.'); + advance('id'); + advance('('); + if (nexttoken.value !== adsafe_id) { + error('ADsafe violation: id does not match.', nexttoken); + } + advance('(string)'); + advance(')'); + advance(';'); + adsafe_may = true; + } + break; + case 'lib': + if (nexttoken.value === 'ADSAFE') { + advance('ADSAFE'); + advance('.'); + advance('lib'); + advance('('); + advance('(string)'); + comma(); + f = expression(0); + if (f.id !== 'function') { + error('The second argument to lib must be a function.', f); + } + p = f.funct['(params)']; + p = p && p.join(', '); + if (p && p !== 'lib') { + error("Expected '{a}' and instead saw '{b}'.", + f, '(lib)', '(' + p + ')'); + } + advance(')'); + advance(';'); + return a; + } else { + error("ADsafe lib violation."); + } + } + } + while (!nexttoken.reach && nexttoken.id !== '(end)') { + if (nexttoken.id === ';') { + warning("Unnecessary semicolon."); + advance(';'); + } else { + a.push(statement()); + } + } + return a; + } + + + /* + * Parses a single block. A block is a sequence of statements wrapped in + * braces. + * + * ordinary - true for everything but function bodies and try blocks. + * stmt - true if block can be a single statement (e.g. in if/for/while). + */ + function block(ordinary, stmt) { + var a, + b = inblock, + old_indent = indent, + m = strict_mode, + s = scope, + t; + + inblock = ordinary; + scope = Object.create(scope); + nonadjacent(token, nexttoken); + t = nexttoken; + + if (nexttoken.id === '{') { + advance('{'); + if (nexttoken.id !== '}' || token.line !== nexttoken.line) { + indent += option.indent; + while (!ordinary && nexttoken.from > indent) { + indent += option.indent; + } + if (!ordinary && !use_strict() && !m && option.strict && + funct['(context)']['(global)']) { + warning("Missing \"use strict\" statement."); + } + a = statements(); + strict_mode = m; + indent -= option.indent; + indentation(); + } + advance('}', t); + indent = old_indent; + } else if (!ordinary) { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, '{', nexttoken.value); + } else { + if (!stmt || option.curly) + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, '{', nexttoken.value); + + noreach = true; + a = [statement()]; + noreach = false; + } + funct['(verb)'] = null; + scope = s; + inblock = b; + if (ordinary && option.noempty && (!a || a.length === 0)) { + warning("Empty block."); + } + return a; + } + + + function countMember(m) { + if (membersOnly && typeof membersOnly[m] !== 'boolean') { + warning("Unexpected /*member '{a}'.", token, m); + } + if (typeof member[m] === 'number') { + member[m] += 1; + } else { + member[m] = 1; + } + } + + + function note_implied(token) { + var name = token.value, line = token.line, a = implied[name]; + if (typeof a === 'function') { + a = false; + } + if (!a) { + a = [line]; + implied[name] = a; + } else if (a[a.length - 1] !== line) { + a.push(line); + } + } + + +// CSS parsing. + + function cssName() { + if (nexttoken.identifier) { + advance(); + return true; + } + } + + + function cssNumber() { + if (nexttoken.id === '-') { + advance('-'); + adjacent(); + nolinebreak(); + } + if (nexttoken.type === '(number)') { + advance('(number)'); + return true; + } + } + + + function cssString() { + if (nexttoken.type === '(string)') { + advance(); + return true; + } + } + + + function cssColor() { + var i, number, value; + if (nexttoken.identifier) { + value = nexttoken.value; + if (value === 'rgb' || value === 'rgba') { + advance(); + advance('('); + for (i = 0; i < 3; i += 1) { + if (i) { + advance(','); + } + number = nexttoken.value; + if (nexttoken.type !== '(number)' || number < 0) { + warning("Expected a positive number and instead saw '{a}'", + nexttoken, number); + advance(); + } else { + advance(); + if (nexttoken.id === '%') { + advance('%'); + if (number > 100) { + warning("Expected a percentage and instead saw '{a}'", + token, number); + } + } else { + if (number > 255) { + warning("Expected a small number and instead saw '{a}'", + token, number); + } + } + } + } + if (value === 'rgba') { + advance(','); + number = +nexttoken.value; + if (nexttoken.type !== '(number)' || number < 0 || number > 1) { + warning("Expected a number between 0 and 1 and instead saw '{a}'", + nexttoken, number); + } + advance(); + if (nexttoken.id === '%') { + warning("Unexpected '%'."); + advance('%'); + } + } + advance(')'); + return true; + } else if (cssColorData[nexttoken.value] === true) { + advance(); + return true; + } + } else if (nexttoken.type === '(color)') { + advance(); + return true; + } + return false; + } + + + function cssLength() { + if (nexttoken.id === '-') { + advance('-'); + adjacent(); + nolinebreak(); + } + if (nexttoken.type === '(number)') { + advance(); + if (nexttoken.type !== '(string)' && + cssLengthData[nexttoken.value] === true) { + adjacent(); + advance(); + } else if (+token.value !== 0) { + warning("Expected a linear unit and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + return true; + } + return false; + } + + + function cssLineHeight() { + if (nexttoken.id === '-') { + advance('-'); + adjacent(); + } + if (nexttoken.type === '(number)') { + advance(); + if (nexttoken.type !== '(string)' && + cssLengthData[nexttoken.value] === true) { + adjacent(); + advance(); + } + return true; + } + return false; + } + + + function cssWidth() { + if (nexttoken.identifier) { + switch (nexttoken.value) { + case 'thin': + case 'medium': + case 'thick': + advance(); + return true; + } + } else { + return cssLength(); + } + } + + + function cssMargin() { + if (nexttoken.identifier) { + if (nexttoken.value === 'auto') { + advance(); + return true; + } + } else { + return cssLength(); + } + } + + function cssAttr() { + if (nexttoken.identifier && nexttoken.value === 'attr') { + advance(); + advance('('); + if (!nexttoken.identifier) { + warning("Expected a name and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + advance(')'); + return true; + } + return false; + } + + + function cssCommaList() { + while (nexttoken.id !== ';') { + if (!cssName() && !cssString()) { + warning("Expected a name and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + if (nexttoken.id !== ',') { + return true; + } + comma(); + } + } + + + function cssCounter() { + if (nexttoken.identifier && nexttoken.value === 'counter') { + advance(); + advance('('); + advance(); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.type !== '(string)') { + warning("Expected a string and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + } + advance(')'); + return true; + } + if (nexttoken.identifier && nexttoken.value === 'counters') { + advance(); + advance('('); + if (!nexttoken.identifier) { + warning("Expected a name and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.type !== '(string)') { + warning("Expected a string and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + } + if (nexttoken.id === ',') { + comma(); + if (nexttoken.type !== '(string)') { + warning("Expected a string and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + } + advance(')'); + return true; + } + return false; + } + + + function cssShape() { + var i; + if (nexttoken.identifier && nexttoken.value === 'rect') { + advance(); + advance('('); + for (i = 0; i < 4; i += 1) { + if (!cssLength()) { + warning("Expected a number and instead saw '{a}'.", + nexttoken, nexttoken.value); + break; + } + } + advance(')'); + return true; + } + return false; + } + + + function cssUrl() { + var c, url; + if (nexttoken.identifier && nexttoken.value === 'url') { + nexttoken = lex.range('(', ')'); + url = nexttoken.value; + c = url.charAt(0); + if (c === '"' || c === '\'') { + if (url.slice(-1) !== c) { + warning("Bad url string."); + } else { + url = url.slice(1, -1); + if (url.indexOf(c) >= 0) { + warning("Bad url string."); + } + } + } + if (!url) { + warning("Missing url."); + } + advance(); + if (option.safe && ux.test(url)) { + error("ADsafe URL violation."); + } + urls.push(url); + return true; + } + return false; + } + + + cssAny = [cssUrl, function () { + for (;;) { + if (nexttoken.identifier) { + switch (nexttoken.value.toLowerCase()) { + case 'url': + cssUrl(); + break; + case 'expression': + warning("Unexpected expression '{a}'.", + nexttoken, nexttoken.value); + advance(); + break; + default: + advance(); + } + } else { + if (nexttoken.id === ';' || nexttoken.id === '!' || + nexttoken.id === '(end)' || nexttoken.id === '}') { + return true; + } + advance(); + } + } + }]; + + + cssBorderStyle = [ + 'none', 'dashed', 'dotted', 'double', 'groove', + 'hidden', 'inset', 'outset', 'ridge', 'solid' + ]; + + cssBreak = [ + 'auto', 'always', 'avoid', 'left', 'right' + ]; + + cssMedia = { + 'all': true, + 'braille': true, + 'embossed': true, + 'handheld': true, + 'print': true, + 'projection': true, + 'screen': true, + 'speech': true, + 'tty': true, + 'tv': true + }; + + cssOverflow = [ + 'auto', 'hidden', 'scroll', 'visible' + ]; + + cssAttributeData = { + background: [ + true, 'background-attachment', 'background-color', + 'background-image', 'background-position', 'background-repeat' + ], + 'background-attachment': ['scroll', 'fixed'], + 'background-color': ['transparent', cssColor], + 'background-image': ['none', cssUrl], + 'background-position': [ + 2, [cssLength, 'top', 'bottom', 'left', 'right', 'center'] + ], + 'background-repeat': [ + 'repeat', 'repeat-x', 'repeat-y', 'no-repeat' + ], + 'border': [true, 'border-color', 'border-style', 'border-width'], + 'border-bottom': [ + true, 'border-bottom-color', 'border-bottom-style', + 'border-bottom-width' + ], + 'border-bottom-color': cssColor, + 'border-bottom-style': cssBorderStyle, + 'border-bottom-width': cssWidth, + 'border-collapse': ['collapse', 'separate'], + 'border-color': ['transparent', 4, cssColor], + 'border-left': [ + true, 'border-left-color', 'border-left-style', 'border-left-width' + ], + 'border-left-color': cssColor, + 'border-left-style': cssBorderStyle, + 'border-left-width': cssWidth, + 'border-right': [ + true, 'border-right-color', 'border-right-style', + 'border-right-width' + ], + 'border-right-color': cssColor, + 'border-right-style': cssBorderStyle, + 'border-right-width': cssWidth, + 'border-spacing': [2, cssLength], + 'border-style': [4, cssBorderStyle], + 'border-top': [ + true, 'border-top-color', 'border-top-style', 'border-top-width' + ], + 'border-top-color': cssColor, + 'border-top-style': cssBorderStyle, + 'border-top-width': cssWidth, + 'border-width': [4, cssWidth], + bottom: [cssLength, 'auto'], + 'caption-side' : ['bottom', 'left', 'right', 'top'], + clear: ['both', 'left', 'none', 'right'], + clip: [cssShape, 'auto'], + color: cssColor, + content: [ + 'open-quote', 'close-quote', 'no-open-quote', 'no-close-quote', + cssString, cssUrl, cssCounter, cssAttr + ], + 'counter-increment': [ + cssName, 'none' + ], + 'counter-reset': [ + cssName, 'none' + ], + cursor: [ + cssUrl, 'auto', 'crosshair', 'default', 'e-resize', 'help', 'move', + 'n-resize', 'ne-resize', 'nw-resize', 'pointer', 's-resize', + 'se-resize', 'sw-resize', 'w-resize', 'text', 'wait' + ], + direction: ['ltr', 'rtl'], + display: [ + 'block', 'compact', 'inline', 'inline-block', 'inline-table', + 'list-item', 'marker', 'none', 'run-in', 'table', 'table-caption', + 'table-cell', 'table-column', 'table-column-group', + 'table-footer-group', 'table-header-group', 'table-row', + 'table-row-group' + ], + 'empty-cells': ['show', 'hide'], + 'float': ['left', 'none', 'right'], + font: [ + 'caption', 'icon', 'menu', 'message-box', 'small-caption', + 'status-bar', true, 'font-size', 'font-style', 'font-weight', + 'font-family' + ], + 'font-family': cssCommaList, + 'font-size': [ + 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', + 'xx-large', 'larger', 'smaller', cssLength + ], + 'font-size-adjust': ['none', cssNumber], + 'font-stretch': [ + 'normal', 'wider', 'narrower', 'ultra-condensed', + 'extra-condensed', 'condensed', 'semi-condensed', + 'semi-expanded', 'expanded', 'extra-expanded' + ], + 'font-style': [ + 'normal', 'italic', 'oblique' + ], + 'font-variant': [ + 'normal', 'small-caps' + ], + 'font-weight': [ + 'normal', 'bold', 'bolder', 'lighter', cssNumber + ], + height: [cssLength, 'auto'], + left: [cssLength, 'auto'], + 'letter-spacing': ['normal', cssLength], + 'line-height': ['normal', cssLineHeight], + 'list-style': [ + true, 'list-style-image', 'list-style-position', 'list-style-type' + ], + 'list-style-image': ['none', cssUrl], + 'list-style-position': ['inside', 'outside'], + 'list-style-type': [ + 'circle', 'disc', 'square', 'decimal', 'decimal-leading-zero', + 'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha', + 'lower-latin', 'upper-alpha', 'upper-latin', 'hebrew', 'katakana', + 'hiragana-iroha', 'katakana-oroha', 'none' + ], + margin: [4, cssMargin], + 'margin-bottom': cssMargin, + 'margin-left': cssMargin, + 'margin-right': cssMargin, + 'margin-top': cssMargin, + 'marker-offset': [cssLength, 'auto'], + 'max-height': [cssLength, 'none'], + 'max-width': [cssLength, 'none'], + 'min-height': cssLength, + 'min-width': cssLength, + opacity: cssNumber, + outline: [true, 'outline-color', 'outline-style', 'outline-width'], + 'outline-color': ['invert', cssColor], + 'outline-style': [ + 'dashed', 'dotted', 'double', 'groove', 'inset', 'none', + 'outset', 'ridge', 'solid' + ], + 'outline-width': cssWidth, + overflow: cssOverflow, + 'overflow-x': cssOverflow, + 'overflow-y': cssOverflow, + padding: [4, cssLength], + 'padding-bottom': cssLength, + 'padding-left': cssLength, + 'padding-right': cssLength, + 'padding-top': cssLength, + 'page-break-after': cssBreak, + 'page-break-before': cssBreak, + position: ['absolute', 'fixed', 'relative', 'static'], + quotes: [8, cssString], + right: [cssLength, 'auto'], + 'table-layout': ['auto', 'fixed'], + 'text-align': ['center', 'justify', 'left', 'right'], + 'text-decoration': [ + 'none', 'underline', 'overline', 'line-through', 'blink' + ], + 'text-indent': cssLength, + 'text-shadow': ['none', 4, [cssColor, cssLength]], + 'text-transform': ['capitalize', 'uppercase', 'lowercase', 'none'], + top: [cssLength, 'auto'], + 'unicode-bidi': ['normal', 'embed', 'bidi-override'], + 'vertical-align': [ + 'baseline', 'bottom', 'sub', 'super', 'top', 'text-top', 'middle', + 'text-bottom', cssLength + ], + visibility: ['visible', 'hidden', 'collapse'], + 'white-space': [ + 'normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap', 'inherit' + ], + width: [cssLength, 'auto'], + 'word-spacing': ['normal', cssLength], + 'word-wrap': ['break-word', 'normal'], + 'z-index': ['auto', cssNumber] + }; + + function styleAttribute() { + var v; + while (nexttoken.id === '*' || nexttoken.id === '#' || + nexttoken.value === '_') { + if (!option.css) { + warning("Unexpected '{a}'.", nexttoken, nexttoken.value); + } + advance(); + } + if (nexttoken.id === '-') { + if (!option.css) { + warning("Unexpected '{a}'.", nexttoken, nexttoken.value); + } + advance('-'); + if (!nexttoken.identifier) { + warning( +"Expected a non-standard style attribute and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + return cssAny; + } else { + if (!nexttoken.identifier) { + warning("Excepted a style attribute, and instead saw '{a}'.", + nexttoken, nexttoken.value); + } else { + if (is_own(cssAttributeData, nexttoken.value)) { + v = cssAttributeData[nexttoken.value]; + } else { + v = cssAny; + if (!option.css) { + warning("Unrecognized style attribute '{a}'.", + nexttoken, nexttoken.value); + } + } + } + advance(); + return v; + } + } + + + function styleValue(v) { + var i = 0, + n, + once, + match, + round, + start = 0, + vi; + switch (typeof v) { + case 'function': + return v(); + case 'string': + if (nexttoken.identifier && nexttoken.value === v) { + advance(); + return true; + } + return false; + } + for (;;) { + if (i >= v.length) { + return false; + } + vi = v[i]; + i += 1; + if (vi === true) { + break; + } else if (typeof vi === 'number') { + n = vi; + vi = v[i]; + i += 1; + } else { + n = 1; + } + match = false; + while (n > 0) { + if (styleValue(vi)) { + match = true; + n -= 1; + } else { + break; + } + } + if (match) { + return true; + } + } + start = i; + once = []; + for (;;) { + round = false; + for (i = start; i < v.length; i += 1) { + if (!once[i]) { + if (styleValue(cssAttributeData[v[i]])) { + match = true; + round = true; + once[i] = true; + break; + } + } + } + if (!round) { + return match; + } + } + } + + function styleChild() { + if (nexttoken.id === '(number)') { + advance(); + if (nexttoken.value === 'n' && nexttoken.identifier) { + adjacent(); + advance(); + if (nexttoken.id === '+') { + adjacent(); + advance('+'); + adjacent(); + advance('(number)'); + } + } + return; + } else { + switch (nexttoken.value) { + case 'odd': + case 'even': + if (nexttoken.identifier) { + advance(); + return; + } + } + } + warning("Unexpected token '{a}'.", nexttoken, nexttoken.value); + } + + function substyle() { + var v; + for (;;) { + if (nexttoken.id === '}' || nexttoken.id === '(end)' || + xquote && nexttoken.id === xquote) { + return; + } + while (nexttoken.id === ';') { + warning("Misplaced ';'."); + advance(';'); + } + v = styleAttribute(); + advance(':'); + if (nexttoken.identifier && nexttoken.value === 'inherit') { + advance(); + } else { + if (!styleValue(v)) { + warning("Unexpected token '{a}'.", nexttoken, + nexttoken.value); + advance(); + } + } + if (nexttoken.id === '!') { + advance('!'); + adjacent(); + if (nexttoken.identifier && nexttoken.value === 'important') { + advance(); + } else { + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, 'important', nexttoken.value); + } + } + if (nexttoken.id === '}' || nexttoken.id === xquote) { + warning("Missing '{a}'.", nexttoken, ';'); + } else { + advance(';'); + } + } + } + + function styleSelector() { + if (nexttoken.identifier) { + if (!is_own(htmltag, option.cap ? + nexttoken.value.toLowerCase() : nexttoken.value)) { + warning("Expected a tagName, and instead saw {a}.", + nexttoken, nexttoken.value); + } + advance(); + } else { + switch (nexttoken.id) { + case '>': + case '+': + advance(); + styleSelector(); + break; + case ':': + advance(':'); + switch (nexttoken.value) { + case 'active': + case 'after': + case 'before': + case 'checked': + case 'disabled': + case 'empty': + case 'enabled': + case 'first-child': + case 'first-letter': + case 'first-line': + case 'first-of-type': + case 'focus': + case 'hover': + case 'last-child': + case 'last-of-type': + case 'link': + case 'only-of-type': + case 'root': + case 'target': + case 'visited': + advance(); + break; + case 'lang': + advance(); + advance('('); + if (!nexttoken.identifier) { + warning("Expected a lang code, and instead saw :{a}.", + nexttoken, nexttoken.value); + } + advance(')'); + break; + case 'nth-child': + case 'nth-last-child': + case 'nth-last-of-type': + case 'nth-of-type': + advance(); + advance('('); + styleChild(); + advance(')'); + break; + case 'not': + advance(); + advance('('); + if (nexttoken.id === ':' && peek(0).value === 'not') { + warning("Nested not."); + } + styleSelector(); + advance(')'); + break; + default: + warning("Expected a pseudo, and instead saw :{a}.", + nexttoken, nexttoken.value); + } + break; + case '#': + advance('#'); + if (!nexttoken.identifier) { + warning("Expected an id, and instead saw #{a}.", + nexttoken, nexttoken.value); + } + advance(); + break; + case '*': + advance('*'); + break; + case '.': + advance('.'); + if (!nexttoken.identifier) { + warning("Expected a class, and instead saw #.{a}.", + nexttoken, nexttoken.value); + } + advance(); + break; + case '[': + advance('['); + if (!nexttoken.identifier) { + warning("Expected an attribute, and instead saw [{a}].", + nexttoken, nexttoken.value); + } + advance(); + if (nexttoken.id === '=' || nexttoken.value === '~=' || + nexttoken.value === '$=' || + nexttoken.value === '|=' || + nexttoken.id === '*=' || + nexttoken.id === '^=') { + advance(); + if (nexttoken.type !== '(string)') { + warning("Expected a string, and instead saw {a}.", + nexttoken, nexttoken.value); + } + advance(); + } + advance(']'); + break; + default: + error("Expected a CSS selector, and instead saw {a}.", + nexttoken, nexttoken.value); + } + } + } + + function stylePattern() { + if (nexttoken.id === '{') { + warning("Expected a style pattern, and instead saw '{a}'.", nexttoken, + nexttoken.id); + } + for (;;) { + styleSelector(); + if (nexttoken.id === ' fragments and .js files.", token); + } + if (option.fragment) { + if (n !== 'div') { + error("ADsafe violation: Wrap the widget in a div.", token); + } + } else { + error("Use the fragment option.", token); + } + } + option.browser = true; + assume(); + } + + function doAttribute(n, a, v) { + var u, x; + if (a === 'id') { + u = typeof v === 'string' ? v.toUpperCase() : ''; + if (ids[u] === true) { + warning("Duplicate id='{a}'.", nexttoken, v); + } + if (!/^[A-Za-z][A-Za-z0-9._:\-]*$/.test(v)) { + warning("Bad id: '{a}'.", nexttoken, v); + } else if (option.adsafe) { + if (adsafe_id) { + if (v.slice(0, adsafe_id.length) !== adsafe_id) { + warning("ADsafe violation: An id must have a '{a}' prefix", + nexttoken, adsafe_id); + } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) { + warning("ADSAFE violation: bad id."); + } + } else { + adsafe_id = v; + if (!/^[A-Z]+_$/.test(v)) { + warning("ADSAFE violation: bad id."); + } + } + } + x = v.search(dx); + if (x >= 0) { + warning("Unexpected character '{a}' in {b}.", token, v.charAt(x), a); + } + ids[u] = true; + } else if (a === 'class' || a === 'type' || a === 'name') { + x = v.search(qx); + if (x >= 0) { + warning("Unexpected character '{a}' in {b}.", token, v.charAt(x), a); + } + ids[u] = true; + } else if (a === 'href' || a === 'background' || + a === 'content' || a === 'data' || + a.indexOf('src') >= 0 || a.indexOf('url') >= 0) { + if (option.safe && ux.test(v)) { + error("ADsafe URL violation."); + } + urls.push(v); + } else if (a === 'for') { + if (option.adsafe) { + if (adsafe_id) { + if (v.slice(0, adsafe_id.length) !== adsafe_id) { + warning("ADsafe violation: An id must have a '{a}' prefix", + nexttoken, adsafe_id); + } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) { + warning("ADSAFE violation: bad id."); + } + } else { + warning("ADSAFE violation: bad id."); + } + } + } else if (a === 'name') { + if (option.adsafe && v.indexOf('_') >= 0) { + warning("ADsafe name violation."); + } + } + } + + function doTag(n, a) { + var i, t = htmltag[n], x; + src = false; + if (!t) { + error("Unrecognized tag '<{a}>'.", + nexttoken, + n === n.toLowerCase() ? n : + n + ' (capitalization error)'); + } + if (stack.length > 0) { + if (n === 'html') { + error("Too many tags.", token); + } + x = t.parent; + if (x) { + if (x.indexOf(' ' + stack[stack.length - 1].name + ' ') < 0) { + error("A '<{a}>' must be within '<{b}>'.", + token, n, x); + } + } else if (!option.adsafe && !option.fragment) { + i = stack.length; + do { + if (i <= 0) { + error("A '<{a}>' must be within '<{b}>'.", + token, n, 'body'); + } + i -= 1; + } while (stack[i].name !== 'body'); + } + } + switch (n) { + case 'div': + if (option.adsafe && stack.length === 1 && !adsafe_id) { + warning("ADSAFE violation: missing ID_."); + } + break; + case 'script': + xmode = 'script'; + advance('>'); + indent = nexttoken.from; + if (a.lang) { + warning("lang is deprecated.", token); + } + if (option.adsafe && stack.length !== 1) { + warning("ADsafe script placement violation.", token); + } + if (a.src) { + if (option.adsafe && (!adsafe_may || !approved[a.src])) { + warning("ADsafe unapproved script source.", token); + } + if (a.type) { + warning("type is unnecessary.", token); + } + } else { + if (adsafe_went) { + error("ADsafe script violation.", token); + } + use_strict(); + statements('script'); + } + xmode = 'html'; + advance(''); + styles(); + xmode = 'html'; + advance(''; + } + + function html() { + var a, attributes, e, n, q, t, v, w = option.white, wmode; + xmode = 'html'; + xquote = ''; + stack = null; + for (;;) { + switch (nexttoken.value) { + case '<': + xmode = 'html'; + advance('<'); + attributes = {}; + t = nexttoken; + if (!t.identifier) { + warning("Bad identifier {a}.", t, t.value); + } + n = t.value; + if (option.cap) { + n = n.toLowerCase(); + } + t.name = n; + advance(); + if (!stack) { + stack = []; + doBegin(n); + } + v = htmltag[n]; + if (typeof v !== 'object') { + error("Unrecognized tag '<{a}>'.", t, n); + } + e = v.empty; + t.type = n; + for (;;) { + if (nexttoken.id === '/') { + advance('/'); + if (nexttoken.id !== '>') { + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, '>', nexttoken.value); + } + break; + } + if (nexttoken.id && nexttoken.id.substr(0, 1) === '>') { + break; + } + if (!nexttoken.identifier) { + if (nexttoken.id === '(end)' || nexttoken.id === '(error)') { + error("Missing '>'.", nexttoken); + } + warning("Bad identifier."); + } + option.white = true; + nonadjacent(token, nexttoken); + a = nexttoken.value; + option.white = w; + advance(); + if (!option.cap && a !== a.toLowerCase()) { + warning("Attribute '{a}' not all lower case.", nexttoken, a); + } + a = a.toLowerCase(); + xquote = ''; + if (is_own(attributes, a)) { + warning("Attribute '{a}' repeated.", nexttoken, a); + } + if (a.slice(0, 2) === 'on') { + if (!option.on) { + warning("Avoid HTML event handlers."); + } + xmode = 'scriptstring'; + advance('='); + q = nexttoken.id; + if (q !== '"' && q !== "'") { + error("Missing quote."); + } + xquote = q; + wmode = option.white; + option.white = false; + advance(q); + use_strict(); + statements('on'); + option.white = wmode; + if (nexttoken.id !== q) { + error("Missing close quote on script attribute."); + } + xmode = 'html'; + xquote = ''; + advance(q); + v = false; + } else if (a === 'style') { + xmode = 'scriptstring'; + advance('='); + q = nexttoken.id; + if (q !== '"' && q !== "'") { + error("Missing quote."); + } + xmode = 'styleproperty'; + xquote = q; + advance(q); + substyle(); + xmode = 'html'; + xquote = ''; + advance(q); + v = false; + } else { + if (nexttoken.id === '=') { + advance('='); + v = nexttoken.value; + if (!nexttoken.identifier && + nexttoken.id !== '"' && + nexttoken.id !== '\'' && + nexttoken.type !== '(string)' && + nexttoken.type !== '(number)' && + nexttoken.type !== '(color)') { + warning("Expected an attribute value and instead saw '{a}'.", token, a); + } + advance(); + } else { + v = true; + } + } + attributes[a] = v; + doAttribute(n, a, v); + } + doTag(n, attributes); + if (!e) { + stack.push(t); + } + xmode = 'outer'; + advance('>'); + break; + case '') { + error("Missing '{a}'.", nexttoken, '>'); + } + xmode = 'outer'; + advance('>'); + break; + case '' || nexttoken.id === '(end)') { + break; + } + if (nexttoken.value.indexOf('--') >= 0) { + error("Unexpected --."); + } + if (nexttoken.value.indexOf('<') >= 0) { + error("Unexpected <."); + } + if (nexttoken.value.indexOf('>') >= 0) { + error("Unexpected >."); + } + } + xmode = 'outer'; + advance('>'); + break; + case '(end)': + return; + default: + if (nexttoken.id === '(end)') { + error("Missing '{a}'.", nexttoken, + ''); + } else { + advance(); + } + } + if (stack && stack.length === 0 && (option.adsafe || + !option.fragment || nexttoken.id === '(end)')) { + break; + } + } + if (nexttoken.id !== '(end)') { + error("Unexpected material after the end."); + } + } + + +// Build the syntax table by declaring the syntactic elements of the language. + + type('(number)', function () { + return this; + }); + type('(string)', function () { + return this; + }); + + syntax['(identifier)'] = { + type: '(identifier)', + lbp: 0, + identifier: true, + nud: function () { + var v = this.value, + s = scope[v], + f; + if (typeof s === 'function') { + +// Protection against accidental inheritance. + + s = undefined; + } else if (typeof s === 'boolean') { + f = funct; + funct = functions[0]; + addlabel(v, 'var'); + s = funct; + funct = f; + } + +// The name is in scope and defined in the current function. + + if (funct === s) { + +// Change 'unused' to 'var', and reject labels. + + switch (funct[v]) { + case 'unused': + funct[v] = 'var'; + break; + case 'unction': + funct[v] = 'function'; + this['function'] = true; + break; + case 'function': + this['function'] = true; + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + break; + } + +// The name is not defined in the function. If we are in the global scope, +// then we have an undefined variable. +// +// Operators typeof and delete do not raise runtime errors even if the base +// object of a reference is null so no need to display warning if we're +// inside of typeof or delete. + + } else if (funct['(global)']) { + if (anonname != 'typeof' && anonname != 'delete' && + option.undef && typeof predefined[v] !== 'boolean') { + warning("'{a}' is not defined.", token, v); + } + note_implied(token); + +// If the name is already defined in the current +// function, but not as outer, then there is a scope error. + + } else { + switch (funct[v]) { + case 'closure': + case 'function': + case 'var': + case 'unused': + warning("'{a}' used out of scope.", token, v); + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + break; + case 'outer': + case 'global': + break; + default: + +// If the name is defined in an outer function, make an outer entry, and if +// it was unused, make it var. + + if (s === true) { + funct[v] = true; + } else if (s === null) { + warning("'{a}' is not allowed.", token, v); + note_implied(token); + } else if (typeof s !== 'object') { + +// Operators typeof and delete do not raise runtime errors even if the base object of +// a reference is null so no need to display warning if we're inside of typeof or delete. + + if (anonname != 'typeof' && anonname != 'delete' && option.undef) { + warning("'{a}' is not defined.", token, v); + } else { + funct[v] = true; + } + note_implied(token); + } else { + switch (s[v]) { + case 'function': + case 'unction': + this['function'] = true; + s[v] = 'closure'; + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'var': + case 'unused': + s[v] = 'closure'; + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'closure': + case 'parameter': + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + } + } + } + } + return this; + }, + led: function () { + error("Expected an operator and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + }; + + type('(regexp)', function () { + return this; + }); + + +// ECMAScript parser + + delim('(endline)'); + delim('(begin)'); + delim('(end)').reach = true; + delim(''); + delim('(error)').reach = true; + delim('}').reach = true; + delim(')'); + delim(']'); + delim('"').reach = true; + delim("'").reach = true; + delim(';'); + delim(':').reach = true; + delim(','); + delim('#'); + delim('@'); + reserve('else'); + reserve('case').reach = true; + reserve('catch'); + reserve('default').reach = true; + reserve('finally'); + reservevar('arguments', function (x) { + if (strict_mode && funct['(global)']) { + warning("Strict violation.", x); + } else if (option.safe) { + warning("ADsafe violation.", x); + } + }); + reservevar('eval', function (x) { + if (option.safe) { + warning("ADsafe violation.", x); + } + }); + reservevar('false'); + reservevar('Infinity'); + reservevar('NaN'); + reservevar('null'); + reservevar('this', function (x) { + if (strict_mode && ((funct['(statement)'] && + funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) { + warning("Strict violation.", x); + } else if (option.safe) { + warning("ADsafe violation.", x); + } + }); + reservevar('true'); + reservevar('undefined'); + assignop('=', 'assign', 20); + assignop('+=', 'assignadd', 20); + assignop('-=', 'assignsub', 20); + assignop('*=', 'assignmult', 20); + assignop('/=', 'assigndiv', 20).nud = function () { + error("A regular expression literal can be confused with '/='."); + }; + assignop('%=', 'assignmod', 20); + bitwiseassignop('&=', 'assignbitand', 20); + bitwiseassignop('|=', 'assignbitor', 20); + bitwiseassignop('^=', 'assignbitxor', 20); + bitwiseassignop('<<=', 'assignshiftleft', 20); + bitwiseassignop('>>=', 'assignshiftright', 20); + bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20); + infix('?', function (left, that) { + that.left = left; + that.right = expression(10); + advance(':'); + that['else'] = expression(10); + return that; + }, 30); + + infix('||', 'or', 40); + infix('&&', 'and', 50); + bitwise('|', 'bitor', 70); + bitwise('^', 'bitxor', 80); + bitwise('&', 'bitand', 90); + relation('==', function (left, right) { + if (option.eqeqeq) { + warning("Expected '{a}' and instead saw '{b}'.", + this, '===', '=='); + } else if (isPoorRelation(left)) { + warning("Use '{a}' to compare with '{b}'.", + this, '===', left.value); + } else if (isPoorRelation(right)) { + warning("Use '{a}' to compare with '{b}'.", + this, '===', right.value); + } + return this; + }); + relation('==='); + relation('!=', function (left, right) { + if (option.eqeqeq) { + warning("Expected '{a}' and instead saw '{b}'.", + this, '!==', '!='); + } else if (isPoorRelation(left)) { + warning("Use '{a}' to compare with '{b}'.", + this, '!==', left.value); + } else if (isPoorRelation(right)) { + warning("Use '{a}' to compare with '{b}'.", + this, '!==', right.value); + } + return this; + }); + relation('!=='); + relation('<'); + relation('>'); + relation('<='); + relation('>='); + bitwise('<<', 'shiftleft', 120); + bitwise('>>', 'shiftright', 120); + bitwise('>>>', 'shiftrightunsigned', 120); + infix('in', 'in', 120); + infix('instanceof', 'instanceof', 120); + infix('+', function (left, that) { + var right = expression(130); + if (left && right && left.id === '(string)' && right.id === '(string)') { + left.value += right.value; + left.character = right.character; + if (jx.test(left.value)) { + warning("JavaScript URL.", left); + } + return left; + } + that.left = left; + that.right = right; + return that; + }, 130); + prefix('+', 'num'); + prefix('+++', function () { + warning("Confusing pluses."); + this.right = expression(150); + this.arity = 'unary'; + return this; + }); + infix('+++', function (left) { + warning("Confusing pluses."); + this.left = left; + this.right = expression(130); + return this; + }, 130); + infix('-', 'sub', 130); + prefix('-', 'neg'); + prefix('---', function () { + warning("Confusing minuses."); + this.right = expression(150); + this.arity = 'unary'; + return this; + }); + infix('---', function (left) { + warning("Confusing minuses."); + this.left = left; + this.right = expression(130); + return this; + }, 130); + infix('*', 'mult', 140); + infix('/', 'div', 140); + infix('%', 'mod', 140); + + suffix('++', 'postinc'); + prefix('++', 'preinc'); + syntax['++'].exps = true; + + suffix('--', 'postdec'); + prefix('--', 'predec'); + syntax['--'].exps = true; + prefix('delete', function () { + var p = expression(0); + if (!p || (p.id !== '.' && p.id !== '[')) { + warning("Variables should not be deleted."); + } + this.first = p; + return this; + }).exps = true; + + + prefix('~', function () { + if (option.bitwise) { + warning("Unexpected '{a}'.", this, '~'); + } + expression(150); + return this; + }); + prefix('!', function () { + this.right = expression(150); + this.arity = 'unary'; + if (bang[this.right.id] === true) { + warning("Confusing use of '{a}'.", this, '!'); + } + return this; + }); + prefix('typeof', 'typeof'); + prefix('new', function () { + var c = expression(155), i; + if (c && c.id !== 'function') { + if (c.identifier) { + c['new'] = true; + switch (c.value) { + case 'Object': + warning("Use the object literal notation {}.", token); + break; + case 'Array': + if (nexttoken.id !== '(') { + warning("Use the array literal notation [].", token); + } else { + advance('('); + if (nexttoken.id === ')') { + warning("Use the array literal notation [].", token); + } else { + expression(0); + } + + advance(')'); + } + this.first = c; + return this; + case 'Number': + case 'String': + case 'Boolean': + case 'Math': + case 'JSON': + warning("Do not use {a} as a constructor.", token, c.value); + break; + case 'Function': + if (!option.evil) { + warning("The Function constructor is eval."); + } + break; + case 'Date': + case 'RegExp': + break; + default: + if (c.id !== 'function') { + i = c.value.substr(0, 1); + if (option.newcap && (i < 'A' || i > 'Z')) { + warning( + "A constructor name should start with an uppercase letter.", + token); + } + } + } + } else { + if (c.id !== '.' && c.id !== '[' && c.id !== '(') { + warning("Bad constructor.", token); + } + } + } else { + warning("Weird construction. Delete 'new'.", this); + } + adjacent(token, nexttoken); + if (nexttoken.id !== '(') { + warning("Missing '()' invoking a constructor."); + } + this.first = c; + return this; + }); + syntax['new'].exps = true; + + infix('.', function (left, that) { + adjacent(prevtoken, token); + nobreak(); + var m = identifier(); + if (typeof m === 'string') { + countMember(m); + } + that.left = left; + that.right = m; + if (option.noarg && left && left.value === 'arguments' && + (m === 'callee' || m === 'caller')) { + warning("Avoid arguments.{a}.", left, m); + } else if (!option.evil && left && left.value === 'document' && + (m === 'write' || m === 'writeln')) { + warning("document.write can be a form of eval.", left); + } else if (option.adsafe) { + if (left && left.value === 'ADSAFE') { + if (m === 'id' || m === 'lib') { + warning("ADsafe violation.", that); + } else if (m === 'go') { + if (xmode !== 'script') { + warning("ADsafe violation.", that); + } else if (adsafe_went || nexttoken.id !== '(' || + peek(0).id !== '(string)' || + peek(0).value !== adsafe_id || + peek(1).id !== ',') { + error("ADsafe violation: go.", that); + } + adsafe_went = true; + adsafe_may = false; + } + } + } + if (!option.evil && (m === 'eval' || m === 'execScript')) { + warning('eval is evil.'); + } else if (option.safe) { + for (;;) { + if (banned[m] === true) { + warning("ADsafe restricted word '{a}'.", token, m); + } + if (typeof predefined[left.value] !== 'boolean' || + nexttoken.id === '(') { + break; + } + if (standard_member[m] === true) { + if (nexttoken.id === '.') { + warning("ADsafe violation.", that); + } + break; + } + if (nexttoken.id !== '.') { + warning("ADsafe violation.", that); + break; + } + advance('.'); + token.left = that; + token.right = m; + that = token; + m = identifier(); + if (typeof m === 'string') { + countMember(m); + } + } + } + return that; + }, 160, true); + + infix('(', function (left, that) { + if (prevtoken.id !== '}' && prevtoken.id !== ')') { + nobreak(prevtoken, token); + } + nospace(); + if (option.immed && !left.immed && left.id === 'function') { + warning("Wrap an immediate function invocation in parentheses " + + "to assist the reader in understanding that the expression " + + "is the result of a function, and not the function itself."); + } + var n = 0, + p = []; + if (left) { + if (left.type === '(identifier)') { + if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { + if (left.value !== 'Number' && left.value !== 'String' && + left.value !== 'Boolean' && + left.value !== 'Date') { + if (left.value === 'Math') { + warning("Math is not a function.", left); + } else if (option.newcap) { + warning( +"Missing 'new' prefix when invoking a constructor.", left); + } + } + } + } else if (left.id === '.') { + if (option.safe && left.left.value === 'Math' && + left.right === 'random') { + warning("ADsafe violation.", left); + } + } + } + if (nexttoken.id !== ')') { + for (;;) { + p[p.length] = expression(10); + n += 1; + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + advance(')'); + nospace(prevtoken, token); + if (typeof left === 'object') { + if (left.value === 'parseInt' && n === 1) { + warning("Missing radix parameter.", left); + } + if (!option.evil) { + if (left.value === 'eval' || left.value === 'Function' || + left.value === 'execScript') { + warning("eval is evil.", left); + } else if (p[0] && p[0].id === '(string)' && + (left.value === 'setTimeout' || + left.value === 'setInterval')) { + warning( + "Implied eval is evil. Pass a function instead of a string.", left); + } + } + if (!left.identifier && left.id !== '.' && left.id !== '[' && + left.id !== '(' && left.id !== '&&' && left.id !== '||' && + left.id !== '?') { + warning("Bad invocation.", left); + } + } + that.left = left; + return that; + }, 155, true).exps = true; + + prefix('(', function () { + nospace(); + if (nexttoken.id === 'function') { + nexttoken.immed = true; + } + var v = expression(0); + advance(')', this); + nospace(prevtoken, token); + if (option.immed && v.id === 'function') { + if (nexttoken.id === '(') { + warning( +"Move the invocation into the parens that contain the function.", nexttoken); + } else { + warning( +"Do not wrap function literals in parens unless they are to be immediately invoked.", + this); + } + } + return v; + }); + + infix('[', function (left, that) { + nobreak(prevtoken, token); + nospace(); + var e = expression(0), s; + if (e && e.type === '(string)') { + if (option.safe && banned[e.value] === true) { + warning("ADsafe restricted word '{a}'.", that, e.value); + } else if (!option.evil && + (e.value === 'eval' || e.value === 'execScript')) { + warning("eval is evil.", that); + } else if (option.safe && + (e.value.charAt(0) === '_' || e.value.charAt(0) === '-')) { + warning("ADsafe restricted subscript '{a}'.", that, e.value); + } + countMember(e.value); + if (!option.sub && ix.test(e.value)) { + s = syntax[e.value]; + if (!s || !s.reserved) { + warning("['{a}'] is better written in dot notation.", + e, e.value); + } + } + } else if (!e || e.type !== '(number)' || e.value < 0) { + if (option.safe) { + warning('ADsafe subscripting.'); + } + } + advance(']', that); + nospace(prevtoken, token); + that.left = left; + that.right = e; + return that; + }, 160, true); + + prefix('[', function () { + var b = token.line !== nexttoken.line; + this.first = []; + if (b) { + indent += option.indent; + if (nexttoken.from === indent + option.indent) { + indent += option.indent; + } + } + while (nexttoken.id !== '(end)') { + while (nexttoken.id === ',') { + warning("Extra comma."); + advance(','); + } + if (nexttoken.id === ']') { + break; + } + if (b && token.line !== nexttoken.line) { + indentation(); + } + this.first.push(expression(10)); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.id === ']' && !option.es5) { + warning("Extra comma.", token); + break; + } + } else { + break; + } + } + if (b) { + indent -= option.indent; + indentation(); + } + advance(']', this); + return this; + }, 160); + + + function property_name() { + var id = optionalidentifier(true); + if (!id) { + if (nexttoken.id === '(string)') { + id = nexttoken.value; + if (option.adsafe && + (id.charAt(0) === '_' || + id.charAt(id.length - 1) === '_')) { + warning("Unexpected {a} in '{b}'.", token, + "dangling '_'", id); + } + advance(); + } else if (nexttoken.id === '(number)') { + id = nexttoken.value.toString(); + advance(); + } + } + return id; + } + + + function functionparams() { + var i, t = nexttoken, p = []; + advance('('); + nospace(); + if (nexttoken.id === ')') { + advance(')'); + nospace(prevtoken, token); + return; + } + for (;;) { + i = identifier(true); + p.push(i); + addlabel(i, 'parameter'); + if (nexttoken.id === ',') { + comma(); + } else { + advance(')', t); + nospace(prevtoken, token); + return p; + } + } + } + + + function doFunction(i, statement) { + var f, s = scope; + scope = Object.create(s); + funct = { + '(name)' : i || '"' + anonname + '"', + '(line)' : nexttoken.line, + '(context)' : funct, + '(breakage)' : 0, + '(loopage)' : 0, + '(scope)' : scope, + '(statement)': statement + }; + f = funct; + token.funct = funct; + functions.push(funct); + if (i) { + addlabel(i, 'function'); + } + funct['(params)'] = functionparams(); + + block(false); + scope = s; + funct['(last)'] = token.line; + funct = funct['(context)']; + return f; + } + + + (function (x) { + x.nud = function () { + var b, f, i, j, p, seen = {}, t; + b = token.line !== nexttoken.line; + if (b) { + indent += option.indent; + if (nexttoken.from === indent + option.indent) { + indent += option.indent; + } + } + for (;;) { + if (nexttoken.id === '}') { + break; + } + if (b) { + indentation(); + } + if (nexttoken.value === 'get' && peek().id !== ':') { + advance('get'); + if (!option.es5) { + error("get/set are ES5 features."); + } + i = property_name(); + if (!i) { + error("Missing property name."); + } + t = nexttoken; + adjacent(token, nexttoken); + f = doFunction(i); + if (funct['(loopage)']) { + warning("Don't make functions within a loop.", t); + } + p = f['(params)']; + if (p) { + warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i); + } + adjacent(token, nexttoken); + advance(','); + indentation(); + advance('set'); + j = property_name(); + if (i !== j) { + error("Expected {a} and instead saw {b}.", token, i, j); + } + t = nexttoken; + adjacent(token, nexttoken); + f = doFunction(i); + p = f['(params)']; + if (!p || p.length !== 1 || p[0] !== 'value') { + warning("Expected (value) in set {a} function.", t, i); + } + } else { + i = property_name(); + if (typeof i !== 'string') { + break; + } + advance(':'); + nonadjacent(token, nexttoken); + expression(10); + } + if (seen[i] === true) { + warning("Duplicate member '{a}'.", nexttoken, i); + } + seen[i] = true; + countMember(i); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.id === ',') { + warning("Extra comma.", token); + } else if (nexttoken.id === '}' && !option.es5) { + warning("Extra comma.", token); + } + } else { + break; + } + } + if (b) { + indent -= option.indent; + indentation(); + } + advance('}', this); + return this; + }; + x.fud = function () { + error("Expected to see a statement and instead saw a block.", token); + }; + }(delim('{'))); + + + var varstatement = function varstatement(prefix) { + +// JavaScript does not have block scope. It only has function scope. So, +// declaring a variable in a block can have unexpected consequences. + + var id, name, value; + + if (funct['(onevar)'] && option.onevar) { + warning("Too many var statements."); + } else if (!funct['(global)']) { + funct['(onevar)'] = true; + } + this.first = []; + for (;;) { + nonadjacent(token, nexttoken); + id = identifier(); + if (funct['(global)'] && predefined[id] === false) { + warning("Redefinition of '{a}'.", token, id); + } + addlabel(id, 'unused'); + if (prefix) { + break; + } + name = token; + this.first.push(token); + if (nexttoken.id === '=') { + nonadjacent(token, nexttoken); + advance('='); + nonadjacent(token, nexttoken); + if (nexttoken.id === 'undefined') { + warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id); + } + if (peek(0).id === '=' && nexttoken.identifier) { + error("Variable {a} was not declared correctly.", + nexttoken, nexttoken.value); + } + value = expression(0); + name.first = value; + } + if (nexttoken.id !== ',') { + break; + } + comma(); + } + return this; + }; + + + stmt('var', varstatement).exps = true; + + + blockstmt('function', function () { + if (inblock) { + warning( +"Function statements should not be placed in blocks. Use a function expression or move the statement to the top of the outer function.", token); + + } + var i = identifier(); + adjacent(token, nexttoken); + addlabel(i, 'unction'); + doFunction(i, true); + if (nexttoken.id === '(' && nexttoken.line === token.line) { + error( +"Function statements are not invocable. Wrap the whole function invocation in parens."); + } + return this; + }); + + prefix('function', function () { + var i = optionalidentifier(); + if (i) { + adjacent(token, nexttoken); + } else { + nonadjacent(token, nexttoken); + } + doFunction(i); + if (funct['(loopage)']) { + warning("Don't make functions within a loop."); + } + return this; + }); + + blockstmt('if', function () { + var t = nexttoken; + advance('('); + nonadjacent(this, t); + nospace(); + expression(20); + if (nexttoken.id === '=') { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + advance(')', t); + nospace(prevtoken, token); + block(true, true); + if (nexttoken.id === 'else') { + nonadjacent(token, nexttoken); + advance('else'); + if (nexttoken.id === 'if' || nexttoken.id === 'switch') { + statement(true); + } else { + block(true, true); + } + } + return this; + }); + + blockstmt('try', function () { + var b, e, s; + if (option.adsafe) { + warning("ADsafe try violation.", this); + } + block(false); + if (nexttoken.id === 'catch') { + advance('catch'); + nonadjacent(token, nexttoken); + advance('('); + s = scope; + scope = Object.create(s); + e = nexttoken.value; + if (nexttoken.type !== '(identifier)') { + warning("Expected an identifier and instead saw '{a}'.", + nexttoken, e); + } else { + addlabel(e, 'exception'); + } + advance(); + advance(')'); + block(false); + b = true; + scope = s; + } + if (nexttoken.id === 'finally') { + advance('finally'); + block(false); + return; + } else if (!b) { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, 'catch', nexttoken.value); + } + return this; + }); + + blockstmt('while', function () { + var t = nexttoken; + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + advance('('); + nonadjacent(this, t); + nospace(); + expression(20); + if (nexttoken.id === '=') { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + advance(')', t); + nospace(prevtoken, token); + block(true, true); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + }).labelled = true; + + reserve('with'); + + blockstmt('switch', function () { + var t = nexttoken, + g = false; + funct['(breakage)'] += 1; + advance('('); + nonadjacent(this, t); + nospace(); + this.condition = expression(20); + advance(')', t); + nospace(prevtoken, token); + nonadjacent(token, nexttoken); + t = nexttoken; + advance('{'); + nonadjacent(token, nexttoken); + indent += option.indent; + this.cases = []; + for (;;) { + switch (nexttoken.id) { + case 'case': + switch (funct['(verb)']) { + case 'break': + case 'case': + case 'continue': + case 'return': + case 'switch': + case 'throw': + break; + default: + // You can tell JSHint that you don't use break intentionally by + // adding a comment /* falls through */ on a line just before + // the next `case`. + if (!ft.test(lines[nexttoken.line - 2])) { + warning( + "Expected a 'break' statement before 'case'.", + token); + } + } + indentation(-option.indent); + advance('case'); + this.cases.push(expression(20)); + g = true; + advance(':'); + funct['(verb)'] = 'case'; + break; + case 'default': + switch (funct['(verb)']) { + case 'break': + case 'continue': + case 'return': + case 'throw': + break; + default: + if (!ft.test(lines[nexttoken.line - 2])) { + warning( + "Expected a 'break' statement before 'default'.", + token); + } + } + indentation(-option.indent); + advance('default'); + g = true; + advance(':'); + break; + case '}': + indent -= option.indent; + indentation(); + advance('}', t); + if (this.cases.length === 1 || this.condition.id === 'true' || + this.condition.id === 'false') { + warning("This 'switch' should be an 'if'.", this); + } + funct['(breakage)'] -= 1; + funct['(verb)'] = undefined; + return; + case '(end)': + error("Missing '{a}'.", nexttoken, '}'); + return; + default: + if (g) { + switch (token.id) { + case ',': + error("Each value should have its own case label."); + return; + case ':': + statements(); + break; + default: + error("Missing ':' on a case clause.", token); + } + } else { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, 'case', nexttoken.value); + } + } + } + }).labelled = true; + + stmt('debugger', function () { + if (!option.debug) { + warning("All 'debugger' statements should be removed."); + } + return this; + }).exps = true; + + (function () { + var x = stmt('do', function () { + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + this.first = block(true); + advance('while'); + var t = nexttoken; + nonadjacent(token, t); + advance('('); + nospace(); + expression(20); + if (nexttoken.id === '=') { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + advance(')', t); + nospace(prevtoken, token); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + }); + x.labelled = true; + x.exps = true; + }()); + + blockstmt('for', function () { + var f = option.forin, s, t = nexttoken; + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + advance('('); + nonadjacent(this, t); + nospace(); + if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') { + if (nexttoken.id === 'var') { + advance('var'); + varstatement(true); + } else { + switch (funct[nexttoken.value]) { + case 'unused': + funct[nexttoken.value] = 'var'; + break; + case 'var': + break; + default: + warning("Bad for in variable '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + } + advance('in'); + expression(20); + advance(')', t); + s = block(true, true); + if (!f && (s.length > 1 || typeof s[0] !== 'object' || + s[0].value !== 'if')) { + warning("The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.", this); + } + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + } else { + if (nexttoken.id !== ';') { + if (nexttoken.id === 'var') { + advance('var'); + varstatement(); + } else { + for (;;) { + expression(0, 'for'); + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + } + nolinebreak(token); + advance(';'); + if (nexttoken.id !== ';') { + expression(20); + if (nexttoken.id === '=') { + if (!option.boss) + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + expression(20); + } + } + nolinebreak(token); + advance(';'); + if (nexttoken.id === ';') { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, ')', ';'); + } + if (nexttoken.id !== ')') { + for (;;) { + expression(0, 'for'); + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + advance(')', t); + nospace(prevtoken, token); + block(true, true); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + } + }).labelled = true; + + + stmt('break', function () { + var v = nexttoken.value; + if (funct['(breakage)'] === 0) { + warning("Unexpected '{a}'.", nexttoken, this.value); + } + nolinebreak(this); + if (nexttoken.id !== ';') { + if (token.line === nexttoken.line) { + if (funct[v] !== 'label') { + warning("'{a}' is not a statement label.", nexttoken, v); + } else if (scope[v] !== funct) { + warning("'{a}' is out of scope.", nexttoken, v); + } + this.first = nexttoken; + advance(); + } + } + reachable('break'); + return this; + }).exps = true; + + + stmt('continue', function () { + var v = nexttoken.value; + if (funct['(breakage)'] === 0) { + warning("Unexpected '{a}'.", nexttoken, this.value); + } + nolinebreak(this); + if (nexttoken.id !== ';') { + if (token.line === nexttoken.line) { + if (funct[v] !== 'label') { + warning("'{a}' is not a statement label.", nexttoken, v); + } else if (scope[v] !== funct) { + warning("'{a}' is out of scope.", nexttoken, v); + } + this.first = nexttoken; + advance(); + } + } else if (!funct['(loopage)']) { + warning("Unexpected '{a}'.", nexttoken, this.value); + } + reachable('continue'); + return this; + }).exps = true; + + + stmt('return', function () { + nolinebreak(this); + if (nexttoken.id === '(regexp)') { + warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator."); + } + if (nexttoken.id !== ';' && !nexttoken.reach) { + nonadjacent(token, nexttoken); + this.first = expression(20); + } + reachable('return'); + return this; + }).exps = true; + + + stmt('throw', function () { + nolinebreak(this); + nonadjacent(token, nexttoken); + this.first = expression(20); + reachable('throw'); + return this; + }).exps = true; + + reserve('void'); + +// Superfluous reserved words + + reserve('class'); + reserve('const'); + reserve('enum'); + reserve('export'); + reserve('extends'); + reserve('import'); + reserve('super'); + + reserve('let'); + reserve('yield'); + reserve('implements'); + reserve('interface'); + reserve('package'); + reserve('private'); + reserve('protected'); + reserve('public'); + reserve('static'); + + +// Parse JSON + + function jsonValue() { + + function jsonObject() { + var o = {}, t = nexttoken; + advance('{'); + if (nexttoken.id !== '}') { + for (;;) { + if (nexttoken.id === '(end)') { + error("Missing '}' to match '{' from line {a}.", + nexttoken, t.line); + } else if (nexttoken.id === '}') { + warning("Unexpected comma.", token); + break; + } else if (nexttoken.id === ',') { + error("Unexpected comma.", nexttoken); + } else if (nexttoken.id !== '(string)') { + warning("Expected a string and instead saw {a}.", + nexttoken, nexttoken.value); + } + if (o[nexttoken.value] === true) { + warning("Duplicate key '{a}'.", + nexttoken, nexttoken.value); + } else if (nexttoken.value === '__proto__') { + warning("Stupid key '{a}'.", + nexttoken, nexttoken.value); + } else { + o[nexttoken.value] = true; + } + advance(); + advance(':'); + jsonValue(); + if (nexttoken.id !== ',') { + break; + } + advance(','); + } + } + advance('}'); + } + + function jsonArray() { + var t = nexttoken; + advance('['); + if (nexttoken.id !== ']') { + for (;;) { + if (nexttoken.id === '(end)') { + error("Missing ']' to match '[' from line {a}.", + nexttoken, t.line); + } else if (nexttoken.id === ']') { + warning("Unexpected comma.", token); + break; + } else if (nexttoken.id === ',') { + error("Unexpected comma.", nexttoken); + } + jsonValue(); + if (nexttoken.id !== ',') { + break; + } + advance(','); + } + } + advance(']'); + } + + switch (nexttoken.id) { + case '{': + jsonObject(); + break; + case '[': + jsonArray(); + break; + case 'true': + case 'false': + case 'null': + case '(number)': + case '(string)': + advance(); + break; + case '-': + advance('-'); + if (token.character !== nexttoken.from) { + warning("Unexpected space after '-'.", token); + } + adjacent(token, nexttoken); + advance('(number)'); + break; + default: + error("Expected a JSON value.", nexttoken); + } + } + + +// The actual JSHINT function itself. + + var itself = function (s, o, g) { + var a, i, k; + JSHINT.errors = []; + predefined = Object.create(standard); + combine(predefined, g || {}); + if (o) { + a = o.predef; + if (a) { + if (Array.isArray(a)) { + for (i = 0; i < a.length; i += 1) { + predefined[a[i]] = true; + } + } else if (typeof a === 'object') { + k = Object.keys(a); + for (i = 0; i < k.length; i += 1) { + predefined[k[i]] = !!a[k]; + } + } + } + if (o.adsafe) { + o.safe = true; + } + if (o.safe) { + o.browser = + o.css = + o.debug = + o.devel = + o.evil = + o.forin = + o.on = + o.rhino = + o.windows = + o.sub = + o.widget = false; + + o.eqeqeq = + o.nomen = + o.safe = + o.undef = true; + + predefined.Date = + predefined['eval'] = + predefined.Function = + predefined.Object = null; + + predefined.ADSAFE = + predefined.lib = false; + } + option = o; + } else { + option = {}; + } + option.indent = option.indent || 4; + option.maxerr = option.maxerr || 50; + adsafe_id = ''; + adsafe_may = false; + adsafe_went = false; + approved = {}; + if (option.approved) { + for (i = 0; i < option.approved.length; i += 1) { + approved[option.approved[i]] = option.approved[i]; + } + } else { + approved.test = 'test'; + } + tab = ''; + for (i = 0; i < option.indent; i += 1) { + tab += ' '; + } + indent = 1; + global = Object.create(predefined); + scope = global; + funct = { + '(global)': true, + '(name)': '(global)', + '(scope)': scope, + '(breakage)': 0, + '(loopage)': 0 + }; + functions = [funct]; + ids = {}; + urls = []; + src = false; + xmode = false; + stack = null; + member = {}; + membersOnly = null; + implied = {}; + inblock = false; + lookahead = []; + jsonmode = false; + warnings = 0; + lex.init(s); + prereg = true; + strict_mode = false; + + prevtoken = token = nexttoken = syntax['(begin)']; + assume(); + + try { + advance(); + if (nexttoken.value.charAt(0) === '<') { + html(); + if (option.adsafe && !adsafe_went) { + warning("ADsafe violation: Missing ADSAFE.go.", this); + } + } else { + switch (nexttoken.id) { + case '{': + case '[': + option.laxbreak = true; + jsonmode = true; + jsonValue(); + break; + case '@': + case '*': + case '#': + case '.': + case ':': + xmode = 'style'; + advance(); + if (token.id !== '@' || !nexttoken.identifier || + nexttoken.value !== 'charset' || token.line !== 1 || + token.from !== 1) { + error("A css file should begin with @charset 'UTF-8';"); + } + advance(); + if (nexttoken.type !== '(string)' && + nexttoken.value !== 'UTF-8') { + error("A css file should begin with @charset 'UTF-8';"); + } + advance(); + advance(';'); + styles(); + break; + + default: + if (option.adsafe && option.fragment) { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, '
', nexttoken.value); + } + if (nexttoken.value === 'use strict') { + warning("Use the function form of \"use strict\"."); + use_strict(); + } + statements('lib'); + } + } + advance('(end)'); + } catch (e) { + if (e) { + JSHINT.errors.push({ + reason : e.message, + line : e.line || nexttoken.line, + character : e.character || nexttoken.from + }, null); + } + } + return JSHINT.errors.length === 0; + }; + + +// Data summary. + + itself.data = function () { + + var data = {functions: []}, fu, globals, implieds = [], f, i, j, + members = [], n, unused = [], v; + if (itself.errors.length) { + data.errors = itself.errors; + } + + if (jsonmode) { + data.json = true; + } + + for (n in implied) { + if (is_own(implied, n)) { + implieds.push({ + name: n, + line: implied[n] + }); + } + } + if (implieds.length > 0) { + data.implieds = implieds; + } + + if (urls.length > 0) { + data.urls = urls; + } + + globals = Object.keys(scope); + if (globals.length > 0) { + data.globals = globals; + } + + for (i = 1; i < functions.length; i += 1) { + f = functions[i]; + fu = {}; + for (j = 0; j < functionicity.length; j += 1) { + fu[functionicity[j]] = []; + } + for (n in f) { + if (is_own(f, n) && n.charAt(0) !== '(') { + v = f[n]; + if (v === 'unction') { + v = 'unused'; + } + if (Array.isArray(fu[v])) { + fu[v].push(n); + if (v === 'unused') { + unused.push({ + name: n, + line: f['(line)'], + 'function': f['(name)'] + }); + } + } + } + } + for (j = 0; j < functionicity.length; j += 1) { + if (fu[functionicity[j]].length === 0) { + delete fu[functionicity[j]]; + } + } + fu.name = f['(name)']; + fu.param = f['(params)']; + fu.line = f['(line)']; + fu.last = f['(last)']; + data.functions.push(fu); + } + + if (unused.length > 0) { + data.unused = unused; + } + + members = []; + for (n in member) { + if (typeof member[n] === 'number') { + data.member = member; + break; + } + } + + return data; + }; + + itself.report = function (option) { + var data = itself.data(); + + var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s; + + function detail(h, array) { + var b, i, singularity; + if (array) { + o.push('
' + h + ' '); + array = array.sort(); + for (i = 0; i < array.length; i += 1) { + if (array[i] !== singularity) { + singularity = array[i]; + o.push((b ? ', ' : '') + singularity); + b = true; + } + } + o.push('
'); + } + } + + + if (data.errors || data.implieds || data.unused) { + err = true; + o.push('
Error:'); + if (data.errors) { + for (i = 0; i < data.errors.length; i += 1) { + c = data.errors[i]; + if (c) { + e = c.evidence || ''; + o.push('

Problem' + (isFinite(c.line) ? ' at line ' + + c.line + ' character ' + c.character : '') + + ': ' + c.reason.entityify() + + '

' + + (e && (e.length > 80 ? e.slice(0, 77) + '...' : + e).entityify()) + '

'); + } + } + } + + if (data.implieds) { + s = []; + for (i = 0; i < data.implieds.length; i += 1) { + s[i] = '' + data.implieds[i].name + ' ' + + data.implieds[i].line + ''; + } + o.push('

Implied global: ' + s.join(', ') + '

'); + } + + if (data.unused) { + s = []; + for (i = 0; i < data.unused.length; i += 1) { + s[i] = '' + data.unused[i].name + ' ' + + data.unused[i].line + ' ' + + data.unused[i]['function'] + ''; + } + o.push('

Unused variable: ' + s.join(', ') + '

'); + } + if (data.json) { + o.push('

JSON: bad.

'); + } + o.push('
'); + } + + if (!option) { + + o.push('
'); + + if (data.urls) { + detail("URLs
", data.urls, '
'); + } + + if (xmode === 'style') { + o.push('

CSS.

'); + } else if (data.json && !err) { + o.push('

JSON: good.

'); + } else if (data.globals) { + o.push('
Global ' + + data.globals.sort().join(', ') + '
'); + } else { + o.push('
No new global variables introduced.
'); + } + + for (i = 0; i < data.functions.length; i += 1) { + f = data.functions[i]; + + o.push('
' + f.line + '-' + + f.last + ' ' + (f.name || '') + '(' + + (f.param ? f.param.join(', ') : '') + ')
'); + detail('Unused', f.unused); + detail('Closure', f.closure); + detail('Variable', f['var']); + detail('Exception', f.exception); + detail('Outer', f.outer); + detail('Global', f.global); + detail('Label', f.label); + } + + if (data.member) { + a = Object.keys(data.member); + if (a.length) { + a = a.sort(); + m = '
/*members ';
+                    l = 10;
+                    for (i = 0; i < a.length; i += 1) {
+                        k = a[i];
+                        n = k.name();
+                        if (l + n.length > 72) {
+                            o.push(m + '
'); + m = ' '; + l = 1; + } + l += n.length + 2; + if (data.member[k] === 1) { + n = '' + n + ''; + } + if (i < a.length - 1) { + n += ', '; + } + m += n; + } + o.push(m + '
*/
'); + } + o.push('
'); + } + } + return o.join(''); + }; + itself.jshint = itself; + + itself.edition = '2011-03-01'; + + return itself; + +}()); + +// Make JSHINT a Node module, if possible. +if (typeof exports == 'object' && exports) + exports.JSHINT = JSHINT; \ No newline at end of file diff --git a/emacs.d/lisp/jshint-mode/jslint.js b/emacs.d/lisp/jshint-mode/jslint.js new file mode 100644 index 0000000..a33278f --- /dev/null +++ b/emacs.d/lisp/jshint-mode/jslint.js @@ -0,0 +1,5701 @@ +// jslint.js +// 2010-09-16 + +/* +Copyright (c) 2002 Douglas Crockford (www.JSLint.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +The Software shall be used for Good, not Evil. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + +/* + JSLINT is a global function. It takes two parameters. + + var myResult = JSLINT(source, option); + + The first parameter is either a string or an array of strings. If it is a + string, it will be split on '\n' or '\r'. If it is an array of strings, it + is assumed that each string represents one line. The source can be a + JavaScript text, or HTML text, or a Konfabulator text. + + The second parameter is an optional object of options which control the + operation of JSLINT. Most of the options are booleans: They are all are + optional and have a default value of false. + + If it checks out, JSLINT returns true. Otherwise, it returns false. + + If false, you can inspect JSLINT.errors to find out the problems. + JSLINT.errors is an array of objects containing these members: + + { + line : The line (relative to 0) at which the lint was found + character : The character (relative to 0) at which the lint was found + reason : The problem + evidence : The text line in which the problem occurred + raw : The raw message before the details were inserted + a : The first detail + b : The second detail + c : The third detail + d : The fourth detail + } + + If a fatal error was found, a null will be the last element of the + JSLINT.errors array. + + You can request a Function Report, which shows all of the functions + and the parameters and vars that they use. This can be used to find + implied global variables and other problems. The report is in HTML and + can be inserted in an HTML . + + var myReport = JSLINT.report(limited); + + If limited is true, then the report will be limited to only errors. + + You can request a data structure which contains JSLint's results. + + var myData = JSLINT.data(); + + It returns a structure with this form: + + { + errors: [ + { + line: NUMBER, + character: NUMBER, + reason: STRING, + evidence: STRING + } + ], + functions: [ + name: STRING, + line: NUMBER, + last: NUMBER, + param: [ + STRING + ], + closure: [ + STRING + ], + var: [ + STRING + ], + exception: [ + STRING + ], + outer: [ + STRING + ], + unused: [ + STRING + ], + global: [ + STRING + ], + label: [ + STRING + ] + ], + globals: [ + STRING + ], + member: { + STRING: NUMBER + }, + unuseds: [ + { + name: STRING, + line: NUMBER + } + ], + implieds: [ + { + name: STRING, + line: NUMBER + } + ], + urls: [ + STRING + ], + json: BOOLEAN + } + + Empty arrays will not be included. + +*/ + +/*jslint + evil: true, nomen: false, onevar: false, regexp: false, strict: true +*/ + +/*members "\b", "\t", "\n", "\f", "\r", "!=", "!==", "\"", "%", + "(begin)", "(breakage)", "(context)", "(error)", "(global)", + "(identifier)", "(last)", "(line)", "(loopage)", "(name)", "(onevar)", + "(params)", "(scope)", "(statement)", "(verb)", "*", "+", "++", "-", + "--", "\/", "<", "<=", "==", "===", ">", ">=", ADSAFE, ActiveXObject, + Array, Boolean, COM, CScript, Canvas, CustomAnimation, Date, Debug, E, + Enumerator, Error, EvalError, FadeAnimation, Flash, FormField, Frame, + Function, HotKey, Image, JSON, LN10, LN2, LOG10E, LOG2E, MAX_VALUE, + MIN_VALUE, Math, MenuItem, MoveAnimation, NEGATIVE_INFINITY, Number, + Object, Option, PI, POSITIVE_INFINITY, Point, RangeError, Rectangle, + ReferenceError, RegExp, ResizeAnimation, RotateAnimation, SQRT1_2, + SQRT2, ScrollBar, String, Style, SyntaxError, System, Text, TextArea, + Timer, TypeError, URIError, URL, VBArray, WScript, Web, Window, XMLDOM, + XMLHttpRequest, "\\", a, abbr, acronym, addEventListener, address, + adsafe, alert, aliceblue, animator, antiquewhite, appleScript, applet, + apply, approved, aqua, aquamarine, area, arguments, arity, article, + aside, audio, autocomplete, azure, b, background, + "background-attachment", "background-color", "background-image", + "background-position", "background-repeat", base, bdo, beep, beige, big, + bisque, bitwise, black, blanchedalmond, block, blockquote, blue, + blueviolet, blur, body, border, "border-bottom", "border-bottom-color", + "border-bottom-style", "border-bottom-width", "border-collapse", + "border-color", "border-left", "border-left-color", "border-left-style", + "border-left-width", "border-right", "border-right-color", + "border-right-style", "border-right-width", "border-spacing", + "border-style", "border-top", "border-top-color", "border-top-style", + "border-top-width", "border-width", bottom, br, brown, browser, + burlywood, button, bytesToUIString, c, cadetblue, call, callee, caller, + canvas, cap, caption, "caption-side", cases, center, charAt, charCodeAt, + character, chartreuse, chocolate, chooseColor, chooseFile, chooseFolder, + cite, clear, clearInterval, clearTimeout, clip, close, closeWidget, + closed, closure, cm, code, col, colgroup, color, command, comment, + condition, confirm, console, constructor, content, convertPathToHFS, + convertPathToPlatform, coral, cornflowerblue, cornsilk, + "counter-increment", "counter-reset", create, crimson, css, cursor, + cyan, d, darkblue, darkcyan, darkgoldenrod, darkgray, darkgreen, + darkkhaki, darkmagenta, darkolivegreen, darkorange, darkorchid, darkred, + darksalmon, darkseagreen, darkslateblue, darkslategray, darkturquoise, + darkviolet, data, datalist, dd, debug, decodeURI, decodeURIComponent, + deeppink, deepskyblue, defaultStatus, defineClass, del, deserialize, + details, devel, dfn, dialog, dimension, dimgray, dir, direction, + display, div, dl, document, dodgerblue, dt, edition, else, em, embed, + empty, "empty-cells", encodeURI, encodeURIComponent, entityify, eqeqeq, + errors, es5, escape, eval, event, evidence, evil, ex, exception, exec, exps, + fieldset, figure, filesystem, firebrick, first, float, floor, + floralwhite, focus, focusWidget, font, "font-face", "font-family", + "font-size", "font-size-adjust", "font-stretch", "font-style", + "font-variant", "font-weight", footer, forestgreen, forin, form, + fragment, frame, frames, frameset, from, fromCharCode, fuchsia, fud, + funct, function, functions, g, gainsboro, gc, getComputedStyle, + ghostwhite, global, globals, gold, goldenrod, gray, green, greenyellow, + h1, h2, h3, h4, h5, h6, hasOwnProperty, head, header, height, help, + hgroup, history, honeydew, hotpink, hr, 'hta:application', html, + i, iTunes, id, identifier, + iframe, img, immed, implieds, in, include, indent, indexOf, indianred, + indigo, init, input, ins, isAlpha, isApplicationRunning, isDigit, + isFinite, isNaN, ivory, join, jslint, json, kbd, keygen, khaki, + konfabulatorVersion, label, labelled, lang, last, lavender, + lavenderblush, lawngreen, laxbreak, lbp, led, left, legend, + lemonchiffon, length, "letter-spacing", li, lib, lightblue, lightcoral, + lightcyan, lightgoldenrodyellow, lightgreen, lightpink, lightsalmon, + lightseagreen, lightskyblue, lightslategray, lightsteelblue, + lightyellow, lime, limegreen, line, "line-height", linen, link, + "list-style", "list-style-image", "list-style-position", + "list-style-type", load, loadClass, location, log, m, magenta, map, + margin, "margin-bottom", "margin-left", "margin-right", "margin-top", + mark, "marker-offset", maroon, match, "max-height", "max-width", maxerr, + maxlen, md5, media, mediumaquamarine, mediumblue, mediumorchid, + mediumpurple, mediumseagreen, mediumslateblue, mediumspringgreen, + mediumturquoise, mediumvioletred, member, menu, message, meta, meter, + midnightblue, "min-height", "min-width", mintcream, mistyrose, mm, + moccasin, moveBy, moveTo, name, nav, navajowhite, navigator, navy, new, + newcap, noframes, nomen, noscript, nud, object, ol, oldlace, olive, + olivedrab, on, onbeforeunload, onblur, onerror, onevar, onfocus, onload, + onresize, onunload, opacity, open, openURL, opener, opera, optgroup, + option, orange, orangered, orchid, outer, outline, "outline-color", + "outline-style", "outline-width", output, overflow, "overflow-x", + "overflow-y", p, padding, "padding-bottom", "padding-left", + "padding-right", "padding-top", page, "page-break-after", + "page-break-before", palegoldenrod, palegreen, paleturquoise, + palevioletred, papayawhip, param, parent, parseFloat, parseInt, + passfail, pc, peachpuff, peru, pink, play, plum, plusplus, pop, + popupMenu, position, powderblue, pre, predef, preferenceGroups, + preferences, print, progress, prompt, prototype, pt, purple, push, px, + q, quit, quotes, random, range, raw, reach, readFile, readUrl, reason, + red, regexp, reloadWidget, removeEventListener, replace, report, + reserved, resizeBy, resizeTo, resolvePath, resumeUpdates, rhino, right, + rosybrown, royalblue, rp, rt, ruby, runCommand, runCommandInBg, + saddlebrown, safe, salmon, samp, sandybrown, saveAs, savePreferences, + screen, script, scroll, scrollBy, scrollTo, seagreen, seal, search, + seashell, section, select, serialize, setInterval, setTimeout, shift, + showWidgetPreferences, sienna, silver, skyblue, slateblue, slategray, + sleep, slice, small, snow, sort, source, span, spawn, speak, split, + springgreen, src, stack, statement, status, steelblue, strict, strong, + style, styleproperty, sub, substr, sup, supplant, suppressUpdates, sync, + system, table, "table-layout", tan, tbody, td, teal, tellWidget, test, + "text-align", "text-decoration", "text-indent", "text-shadow", + "text-transform", textarea, tfoot, th, thead, thistle, time, title, + toLowerCase, toString, toUpperCase, toint32, token, tomato, top, tr, tt, + turquoise, type, u, ul, undef, unescape, "unicode-bidi", unused, + unwatch, updateNow, urls, value, valueOf, var, version, + "vertical-align", video, violet, visibility, watch, wheat, white, + "white-space", whitesmoke, widget, width, windows, "word-spacing", + "word-wrap", yahooCheckLogin, yahooLogin, yahooLogout, yellow, + yellowgreen, "z-index" +*/ + +// We build the application inside a function so that we produce only a single +// global variable. The function will be invoked, its return value is the JSLINT +// application itself. + +"use strict"; + +var JSLINT = (function () { + var adsafe_id, // The widget's ADsafe id. + adsafe_may, // The widget may load approved scripts. + adsafe_went, // ADSAFE.go has been called. + anonname, // The guessed name for anonymous functions. + approved, // ADsafe approved urls. + + atrule = { + media : true, + 'font-face': true, + page : true + }, + +// These are operators that should not be used with the ! operator. + + bang = { + '<': true, + '<=': true, + '==': true, + '===': true, + '!==': true, + '!=': true, + '>': true, + '>=': true, + '+': true, + '-': true, + '*': true, + '/': true, + '%': true + }, + +// These are members that should not be permitted in the safe subset. + + banned = { // the member names that ADsafe prohibits. + 'arguments' : true, + callee : true, + caller : true, + constructor : true, + 'eval' : true, + prototype : true, + stack : true, + unwatch : true, + valueOf : true, + watch : true + }, + + +// These are the JSLint boolean options. + + boolOptions = { + adsafe : true, // if ADsafe should be enforced + bitwise : true, // if bitwise operators should not be allowed + browser : true, // if the standard browser globals should be predefined + cap : true, // if upper case HTML should be allowed + css : true, // if CSS workarounds should be tolerated + debug : true, // if debugger statements should be allowed + devel : true, // if logging should be allowed (console, alert, etc.) + eqeqeq : true, // if === should be required + es5 : true, // if ES5 syntax should be allowed + evil : true, // if eval should be allowed + forin : true, // if for in statements must filter + fragment : true, // if HTML fragments should be allowed + immed : true, // if immediate invocations must be wrapped in parens + laxbreak : true, // if line breaks should not be checked + newcap : true, // if constructor names must be capitalized + nomen : true, // if names should be checked + on : true, // if HTML event handlers should be allowed + onevar : true, // if only one var statement per function should be allowed + passfail : true, // if the scan should stop on first error + plusplus : true, // if increment/decrement should not be allowed + regexp : true, // if the . should not be allowed in regexp literals + rhino : true, // if the Rhino environment globals should be predefined + undef : true, // if variables should be declared before used + safe : true, // if use of some browser features should be restricted + windows : true, // if MS Windows-specigic globals should be predefined + strict : true, // require the "use strict"; pragma + sub : true, // if all forms of subscript notation are tolerated + white : true, // if strict whitespace rules apply + widget : true // if the Yahoo Widgets globals should be predefined + }, + +// browser contains a set of global names which are commonly provided by a +// web browser environment. + + browser = { + addEventListener: false, + blur : false, + clearInterval : false, + clearTimeout : false, + close : false, + closed : false, + defaultStatus : false, + document : false, + event : false, + focus : false, + frames : false, + getComputedStyle: false, + history : false, + Image : false, + length : false, + location : false, + moveBy : false, + moveTo : false, + name : false, + navigator : false, + onbeforeunload : true, + onblur : true, + onerror : true, + onfocus : true, + onload : true, + onresize : true, + onunload : true, + open : false, + opener : false, + Option : false, + parent : false, + print : false, + removeEventListener: false, + resizeBy : false, + resizeTo : false, + screen : false, + scroll : false, + scrollBy : false, + scrollTo : false, + setInterval : false, + setTimeout : false, + status : false, + top : false, + XMLHttpRequest : false + }, + + cssAttributeData, + cssAny, + + cssColorData = { + "aliceblue" : true, + "antiquewhite" : true, + "aqua" : true, + "aquamarine" : true, + "azure" : true, + "beige" : true, + "bisque" : true, + "black" : true, + "blanchedalmond" : true, + "blue" : true, + "blueviolet" : true, + "brown" : true, + "burlywood" : true, + "cadetblue" : true, + "chartreuse" : true, + "chocolate" : true, + "coral" : true, + "cornflowerblue" : true, + "cornsilk" : true, + "crimson" : true, + "cyan" : true, + "darkblue" : true, + "darkcyan" : true, + "darkgoldenrod" : true, + "darkgray" : true, + "darkgreen" : true, + "darkkhaki" : true, + "darkmagenta" : true, + "darkolivegreen" : true, + "darkorange" : true, + "darkorchid" : true, + "darkred" : true, + "darksalmon" : true, + "darkseagreen" : true, + "darkslateblue" : true, + "darkslategray" : true, + "darkturquoise" : true, + "darkviolet" : true, + "deeppink" : true, + "deepskyblue" : true, + "dimgray" : true, + "dodgerblue" : true, + "firebrick" : true, + "floralwhite" : true, + "forestgreen" : true, + "fuchsia" : true, + "gainsboro" : true, + "ghostwhite" : true, + "gold" : true, + "goldenrod" : true, + "gray" : true, + "green" : true, + "greenyellow" : true, + "honeydew" : true, + "hotpink" : true, + "indianred" : true, + "indigo" : true, + "ivory" : true, + "khaki" : true, + "lavender" : true, + "lavenderblush" : true, + "lawngreen" : true, + "lemonchiffon" : true, + "lightblue" : true, + "lightcoral" : true, + "lightcyan" : true, + "lightgoldenrodyellow" : true, + "lightgreen" : true, + "lightpink" : true, + "lightsalmon" : true, + "lightseagreen" : true, + "lightskyblue" : true, + "lightslategray" : true, + "lightsteelblue" : true, + "lightyellow" : true, + "lime" : true, + "limegreen" : true, + "linen" : true, + "magenta" : true, + "maroon" : true, + "mediumaquamarine" : true, + "mediumblue" : true, + "mediumorchid" : true, + "mediumpurple" : true, + "mediumseagreen" : true, + "mediumslateblue" : true, + "mediumspringgreen" : true, + "mediumturquoise" : true, + "mediumvioletred" : true, + "midnightblue" : true, + "mintcream" : true, + "mistyrose" : true, + "moccasin" : true, + "navajowhite" : true, + "navy" : true, + "oldlace" : true, + "olive" : true, + "olivedrab" : true, + "orange" : true, + "orangered" : true, + "orchid" : true, + "palegoldenrod" : true, + "palegreen" : true, + "paleturquoise" : true, + "palevioletred" : true, + "papayawhip" : true, + "peachpuff" : true, + "peru" : true, + "pink" : true, + "plum" : true, + "powderblue" : true, + "purple" : true, + "red" : true, + "rosybrown" : true, + "royalblue" : true, + "saddlebrown" : true, + "salmon" : true, + "sandybrown" : true, + "seagreen" : true, + "seashell" : true, + "sienna" : true, + "silver" : true, + "skyblue" : true, + "slateblue" : true, + "slategray" : true, + "snow" : true, + "springgreen" : true, + "steelblue" : true, + "tan" : true, + "teal" : true, + "thistle" : true, + "tomato" : true, + "turquoise" : true, + "violet" : true, + "wheat" : true, + "white" : true, + "whitesmoke" : true, + "yellow" : true, + "yellowgreen" : true + }, + + cssBorderStyle, + cssBreak, + + cssLengthData = { + '%': true, + 'cm': true, + 'em': true, + 'ex': true, + 'in': true, + 'mm': true, + 'pc': true, + 'pt': true, + 'px': true + }, + + cssOverflow, + + devel = { + alert : false, + confirm : false, + console : false, + Debug : false, + opera : false, + prompt : false + }, + + escapes = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '"' : '\\"', + '/' : '\\/', + '\\': '\\\\' + }, + + funct, // The current function + + functionicity = [ + 'closure', 'exception', 'global', 'label', + 'outer', 'unused', 'var' + ], + + functions, // All of the functions + + global, // The global scope + htmltag = { + a: {}, + abbr: {}, + acronym: {}, + address: {}, + applet: {}, + area: {empty: true, parent: ' map '}, + article: {}, + aside: {}, + audio: {}, + b: {}, + base: {empty: true, parent: ' head '}, + bdo: {}, + big: {}, + blockquote: {}, + body: {parent: ' html noframes '}, + br: {empty: true}, + button: {}, + canvas: {parent: ' body p div th td '}, + caption: {parent: ' table '}, + center: {}, + cite: {}, + code: {}, + col: {empty: true, parent: ' table colgroup '}, + colgroup: {parent: ' table '}, + command: {parent: ' menu '}, + datalist: {}, + dd: {parent: ' dl '}, + del: {}, + details: {}, + dialog: {}, + dfn: {}, + dir: {}, + div: {}, + dl: {}, + dt: {parent: ' dl '}, + em: {}, + embed: {}, + fieldset: {}, + figure: {}, + font: {}, + footer: {}, + form: {}, + frame: {empty: true, parent: ' frameset '}, + frameset: {parent: ' html frameset '}, + h1: {}, + h2: {}, + h3: {}, + h4: {}, + h5: {}, + h6: {}, + head: {parent: ' html '}, + header: {}, + hgroup: {}, + hr: {empty: true}, + 'hta:application': + {empty: true, parent: ' head '}, + html: {parent: '*'}, + i: {}, + iframe: {}, + img: {empty: true}, + input: {empty: true}, + ins: {}, + kbd: {}, + keygen: {}, + label: {}, + legend: {parent: ' details fieldset figure '}, + li: {parent: ' dir menu ol ul '}, + link: {empty: true, parent: ' head '}, + map: {}, + mark: {}, + menu: {}, + meta: {empty: true, parent: ' head noframes noscript '}, + meter: {}, + nav: {}, + noframes: {parent: ' html body '}, + noscript: {parent: ' body head noframes '}, + object: {}, + ol: {}, + optgroup: {parent: ' select '}, + option: {parent: ' optgroup select '}, + output: {}, + p: {}, + param: {empty: true, parent: ' applet object '}, + pre: {}, + progress: {}, + q: {}, + rp: {}, + rt: {}, + ruby: {}, + samp: {}, + script: {empty: true, parent: ' body div frame head iframe p pre span '}, + section: {}, + select: {}, + small: {}, + span: {}, + source: {}, + strong: {}, + style: {parent: ' head ', empty: true}, + sub: {}, + sup: {}, + table: {}, + tbody: {parent: ' table '}, + td: {parent: ' tr '}, + textarea: {}, + tfoot: {parent: ' table '}, + th: {parent: ' tr '}, + thead: {parent: ' table '}, + time: {}, + title: {parent: ' head '}, + tr: {parent: ' table tbody thead tfoot '}, + tt: {}, + u: {}, + ul: {}, + 'var': {}, + video: {} + }, + + ids, // HTML ids + implied, // Implied globals + inblock, + indent, + jsonmode, + lines, + lookahead, + member, + membersOnly, + nexttoken, + noreach, + option, + predefined, // Global variables defined by option + prereg, + prevtoken, + + rhino = { + defineClass : false, + deserialize : false, + gc : false, + help : false, + load : false, + loadClass : false, + print : false, + quit : false, + readFile : false, + readUrl : false, + runCommand : false, + seal : false, + serialize : false, + spawn : false, + sync : false, + toint32 : false, + version : false + }, + + scope, // The current scope + + windows = { + ActiveXObject: false, + CScript : false, + Debug : false, + Enumerator : false, + System : false, + VBArray : false, + WScript : false + }, + + src, + stack, + +// standard contains the global names that are provided by the +// ECMAScript standard. + + standard = { + Array : false, + Boolean : false, + Date : false, + decodeURI : false, + decodeURIComponent : false, + encodeURI : false, + encodeURIComponent : false, + Error : false, + 'eval' : false, + EvalError : false, + Function : false, + hasOwnProperty : false, + isFinite : false, + isNaN : false, + JSON : false, + Math : false, + Number : false, + Object : false, + parseInt : false, + parseFloat : false, + RangeError : false, + ReferenceError : false, + RegExp : false, + String : false, + SyntaxError : false, + TypeError : false, + URIError : false + }, + + standard_member = { + E : true, + LN2 : true, + LN10 : true, + LOG2E : true, + LOG10E : true, + PI : true, + SQRT1_2 : true, + SQRT2 : true, + MAX_VALUE : true, + MIN_VALUE : true, + NEGATIVE_INFINITY : true, + POSITIVE_INFINITY : true + }, + + strict_mode, + syntax = {}, + tab, + token, + urls, + warnings, + +// widget contains the global names which are provided to a Yahoo +// (fna Konfabulator) widget. + + widget = { + alert : true, + animator : true, + appleScript : true, + beep : true, + bytesToUIString : true, + Canvas : true, + chooseColor : true, + chooseFile : true, + chooseFolder : true, + closeWidget : true, + COM : true, + convertPathToHFS : true, + convertPathToPlatform : true, + CustomAnimation : true, + escape : true, + FadeAnimation : true, + filesystem : true, + Flash : true, + focusWidget : true, + form : true, + FormField : true, + Frame : true, + HotKey : true, + Image : true, + include : true, + isApplicationRunning : true, + iTunes : true, + konfabulatorVersion : true, + log : true, + md5 : true, + MenuItem : true, + MoveAnimation : true, + openURL : true, + play : true, + Point : true, + popupMenu : true, + preferenceGroups : true, + preferences : true, + print : true, + prompt : true, + random : true, + Rectangle : true, + reloadWidget : true, + ResizeAnimation : true, + resolvePath : true, + resumeUpdates : true, + RotateAnimation : true, + runCommand : true, + runCommandInBg : true, + saveAs : true, + savePreferences : true, + screen : true, + ScrollBar : true, + showWidgetPreferences : true, + sleep : true, + speak : true, + Style : true, + suppressUpdates : true, + system : true, + tellWidget : true, + Text : true, + TextArea : true, + Timer : true, + unescape : true, + updateNow : true, + URL : true, + Web : true, + widget : true, + Window : true, + XMLDOM : true, + XMLHttpRequest : true, + yahooCheckLogin : true, + yahooLogin : true, + yahooLogout : true + }, + +// xmode is used to adapt to the exceptions in html parsing. +// It can have these states: +// false .js script file +// html +// outer +// script +// style +// scriptstring +// styleproperty + + xmode, + xquote, + +// unsafe comment or string + ax = /@cc|<\/?|script|\]*s\]|<\s*!|</i, +// unsafe characters that are silently deleted by one or more browsers + cx = /[\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/, +// token + tx = /^\s*([(){}\[.,:;'"~\?\]#@]|==?=?|\/(\*(jslint|members?|global)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|%=?|&[&=]?|\|[|=]?|>>?>?=?|<([\/=!]|\!(\[|--)?|<=?)?|\^=?|\!=?=?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+([xX][0-9a-fA-F]+|\.[0-9]*)?([eE][+\-]?[0-9]+)?)/, +// html token + hx = /^\s*(['"=>\/&#]|<(?:\/|\!(?:--)?)?|[a-zA-Z][a-zA-Z0-9_\-:]*|[0-9]+|--)/, +// characters in strings that need escapement + nx = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/, + nxg = /[\u0000-\u001f&<"\/\\\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g, +// outer html token + ox = /[>&]|<[\/!]?|--/, +// star slash + lx = /\*\/|\/\*/, +// identifier + ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/, +// javascript url + jx = /^(?:javascript|jscript|ecmascript|vbscript|mocha|livescript)\s*:/i, +// url badness + ux = /&|\+|\u00AD|\.\.|\/\*|%[^;]|base64|url|expression|data|mailto/i, +// style + sx = /^\s*([{:#%.=,>+\[\]@()"';]|\*=?|\$=|\|=|\^=|~=|[a-zA-Z_][a-zA-Z0-9_\-]*|[0-9]+|<\/|\/\*)/, + ssx = /^\s*([@#!"'};:\-%.=,+\[\]()*_]|[a-zA-Z][a-zA-Z0-9._\-]*|\/\*?|\d+(?:\.\d+)?|<\/)/, +// attributes characters + qx = /[^a-zA-Z0-9+\-_\/ ]/, +// query characters for ids + dx = /[\[\]\/\\"'*<>.&:(){}+=#]/, + + rx = { + outer: hx, + html: hx, + style: sx, + styleproperty: ssx + }; + + function F() {} + + if (typeof Object.create !== 'function') { + Object.create = function (o) { + F.prototype = o; + return new F(); + }; + } + + + function is_own(object, name) { + return Object.prototype.hasOwnProperty.call(object, name); + } + + + function combine(t, o) { + var n; + for (n in o) { + if (is_own(o, n)) { + t[n] = o[n]; + } + } + } + + String.prototype.entityify = function () { + return this + .replace(/&/g, '&') + .replace(//g, '>'); + }; + + String.prototype.isAlpha = function () { + return (this >= 'a' && this <= 'z\uffff') || + (this >= 'A' && this <= 'Z\uffff'); + }; + + + String.prototype.isDigit = function () { + return (this >= '0' && this <= '9'); + }; + + + String.prototype.supplant = function (o) { + return this.replace(/\{([^{}]*)\}/g, function (a, b) { + var r = o[b]; + return typeof r === 'string' || typeof r === 'number' ? r : a; + }); + }; + + String.prototype.name = function () { + +// If the string looks like an identifier, then we can return it as is. +// If the string contains no control characters, no quote characters, and no +// backslash characters, then we can simply slap some quotes around it. +// Otherwise we must also replace the offending characters with safe +// sequences. + + if (ix.test(this)) { + return this; + } + if (nx.test(this)) { + return '"' + this.replace(nxg, function (a) { + var c = escapes[a]; + if (c) { + return c; + } + return '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4); + }) + '"'; + } + return '"' + this + '"'; + }; + + + function assume() { + if (!option.safe) { + if (option.rhino) { + combine(predefined, rhino); + } + if (option.devel) { + combine(predefined, devel); + } + if (option.browser) { + combine(predefined, browser); + } + if (option.windows) { + combine(predefined, windows); + } + if (option.widget) { + combine(predefined, widget); + } + } + } + + +// Produce an error warning. + + function quit(m, l, ch) { + throw { + name: 'JSLintError', + line: l, + character: ch, + message: m + " (" + Math.floor((l / lines.length) * 100) + + "% scanned)." + }; + } + + function warning(m, t, a, b, c, d) { + var ch, l, w; + t = t || nexttoken; + if (t.id === '(end)') { // `~ + t = token; + } + l = t.line || 0; + ch = t.from || 0; + w = { + id: '(error)', + raw: m, + evidence: lines[l - 1] || '', + line: l, + character: ch, + a: a, + b: b, + c: c, + d: d + }; + w.reason = m.supplant(w); + JSLINT.errors.push(w); + if (option.passfail) { + quit('Stopping. ', l, ch); + } + warnings += 1; + if (warnings >= option.maxerr) { + quit("Too many errors.", l, ch); + } + return w; + } + + function warningAt(m, l, ch, a, b, c, d) { + return warning(m, { + line: l, + from: ch + }, a, b, c, d); + } + + function error(m, t, a, b, c, d) { + var w = warning(m, t, a, b, c, d); + quit("Stopping, unable to continue.", w.line, w.character); + } + + function errorAt(m, l, ch, a, b, c, d) { + return error(m, { + line: l, + from: ch + }, a, b, c, d); + } + + + +// lexical analysis + + var lex = (function lex() { + var character, from, line, s; + +// Private lex methods + + function nextLine() { + var at; + if (line >= lines.length) { + return false; + } + character = 1; + s = lines[line]; + line += 1; + at = s.search(/ \t/); + if (at >= 0) { + warningAt("Mixed spaces and tabs.", line, at + 1); + } + s = s.replace(/\t/g, tab); + at = s.search(cx); + if (at >= 0) { + warningAt("Unsafe character.", line, at); + } + if (option.maxlen && option.maxlen < s.length) { + warningAt("Line too long.", line, s.length); + } + return true; + } + +// Produce a token object. The token inherits from a syntax symbol. + + function it(type, value) { + var i, t; + if (type === '(color)') { + t = {type: type}; + } else if (type === '(punctuator)' || + (type === '(identifier)' && is_own(syntax, value))) { + t = syntax[value] || syntax['(error)']; + } else { + t = syntax[type]; + } + t = Object.create(t); + if (type === '(string)' || type === '(range)') { + if (jx.test(value)) { + warningAt("Script URL.", line, from); + } + } + if (type === '(identifier)') { + t.identifier = true; + if (value === '__iterator__' || value === '__proto__') { + errorAt("Reserved name '{a}'.", + line, from, value); + } else if (option.nomen && + (value.charAt(0) === '_' || + value.charAt(value.length - 1) === '_')) { + warningAt("Unexpected {a} in '{b}'.", line, from, + "dangling '_'", value); + } + } + t.value = value; + t.line = line; + t.character = character; + t.from = from; + i = t.id; + if (i !== '(endline)') { + prereg = i && + (('(,=:[!&|?{};'.indexOf(i.charAt(i.length - 1)) >= 0) || + i === 'return'); + } + return t; + } + +// Public lex methods + + return { + init: function (source) { + if (typeof source === 'string') { + lines = source + .replace(/\r\n/g, '\n') + .replace(/\r/g, '\n') + .split('\n'); + } else { + lines = source; + } + line = 0; + nextLine(); + from = 1; + }, + + range: function (begin, end) { + var c, value = ''; + from = character; + if (s.charAt(0) !== begin) { + errorAt("Expected '{a}' and instead saw '{b}'.", + line, character, begin, s.charAt(0)); + } + for (;;) { + s = s.slice(1); + character += 1; + c = s.charAt(0); + switch (c) { + case '': + errorAt("Missing '{a}'.", line, character, c); + break; + case end: + s = s.slice(1); + character += 1; + return it('(range)', value); + case xquote: + case '\\': + warningAt("Unexpected '{a}'.", line, character, c); + } + value += c; + } + + }, + +// token -- this is called by advance to get the next token. + + token: function () { + var b, c, captures, d, depth, high, i, l, low, q, t; + + function match(x) { + var r = x.exec(s), r1; + if (r) { + l = r[0].length; + r1 = r[1]; + c = r1.charAt(0); + s = s.substr(l); + from = character + l - r1.length; + character += l; + return r1; + } + } + + function string(x) { + var c, j, r = ''; + + if (jsonmode && x !== '"') { + warningAt("Strings must use doublequote.", + line, character); + } + + if (xquote === x || (xmode === 'scriptstring' && !xquote)) { + return it('(punctuator)', x); + } + + function esc(n) { + var i = parseInt(s.substr(j + 1, n), 16); + j += n; + if (i >= 32 && i <= 126 && + i !== 34 && i !== 92 && i !== 39) { + warningAt("Unnecessary escapement.", line, character); + } + character += n; + c = String.fromCharCode(i); + } + j = 0; + for (;;) { + while (j >= s.length) { + j = 0; + if (xmode !== 'html' || !nextLine()) { + errorAt("Unclosed string.", line, from); + } + } + c = s.charAt(j); + if (c === x) { + character += 1; + s = s.substr(j + 1); + return it('(string)', r, x); + } + if (c < ' ') { + if (c === '\n' || c === '\r') { + break; + } + warningAt("Control character in string: {a}.", + line, character + j, s.slice(0, j)); + } else if (c === xquote) { + warningAt("Bad HTML string", line, character + j); + } else if (c === '<') { + if (option.safe && xmode === 'html') { + warningAt("ADsafe string violation.", + line, character + j); + } else if (s.charAt(j + 1) === '/' && (xmode || option.safe)) { + warningAt("Expected '<\\/' and instead saw ' 0) { + character += 1; + s = s.slice(i); + break; + } else { + if (!nextLine()) { + return it('(end)', ''); + } + } + } +// t = match(rx[xmode] || tx); +// if (!t) { +// if (xmode === 'html') { +// return it('(error)', s.charAt(0)); +// } else { +// t = ''; +// c = ''; +// while (s && s < '!') { +// s = s.substr(1); +// } +// if (s) { +// errorAt("Unexpected '{a}'.", +// line, character, s.substr(0, 1)); +// } +// } + t = match(rx[xmode] || tx); + if (!t) { + t = ''; + c = ''; + while (s && s < '!') { + s = s.substr(1); + } + if (s) { + if (xmode === 'html') { + return it('(error)', s.charAt(0)); + } else { + errorAt("Unexpected '{a}'.", + line, character, s.substr(0, 1)); + } + } + } else { + + // identifier + + if (c.isAlpha() || c === '_' || c === '$') { + return it('(identifier)', t); + } + + // number + + if (c.isDigit()) { + if (xmode !== 'style' && !isFinite(Number(t))) { + warningAt("Bad number '{a}'.", + line, character, t); + } + if (xmode !== 'style' && + xmode !== 'styleproperty' && + s.substr(0, 1).isAlpha()) { + warningAt("Missing space after '{a}'.", + line, character, t); + } + if (c === '0') { + d = t.substr(1, 1); + if (d.isDigit()) { + if (token.id !== '.' && xmode !== 'styleproperty') { + warningAt("Don't use extra leading zeros '{a}'.", + line, character, t); + } + } else if (jsonmode && (d === 'x' || d === 'X')) { + warningAt("Avoid 0x-. '{a}'.", + line, character, t); + } + } + if (t.substr(t.length - 1) === '.') { + warningAt( + "A trailing decimal point can be confused with a dot '{a}'.", + line, character, t); + } + return it('(number)', t); + } + switch (t) { + + // string + + case '"': + case "'": + return string(t); + + // // comment + + case '//': + if (src || (xmode && xmode !== 'script')) { + warningAt("Unexpected comment.", line, character); + } else if (xmode === 'script' && /<\s*\//i.test(s)) { + warningAt("Unexpected <\/ in comment.", line, character); + } else if ((option.safe || xmode === 'script') && ax.test(s)) { + warningAt("Dangerous comment.", line, character); + } + s = ''; + token.comment = true; + break; + + // /* comment + + case '/*': + if (src || (xmode && xmode !== 'script' && xmode !== 'style' && xmode !== 'styleproperty')) { + warningAt("Unexpected comment.", line, character); + } + if (option.safe && ax.test(s)) { + warningAt("ADsafe comment violation.", line, character); + } + for (;;) { + i = s.search(lx); + if (i >= 0) { + break; + } + if (!nextLine()) { + errorAt("Unclosed comment.", line, character); + } else { + if (option.safe && ax.test(s)) { + warningAt("ADsafe comment violation.", + line, character); + } + } + } + character += i + 2; + if (s.substr(i, 1) === '/') { + errorAt("Nested comment.", line, character); + } + s = s.substr(i + 2); + token.comment = true; + break; + + // /*members /*jslint /*global + + case '/*members': + case '/*member': + case '/*jslint': + case '/*global': + case '*/': + return { + value: t, + type: 'special', + line: line, + character: character, + from: from + }; + + case '': + break; + // / + case '/': + if (token.id === '/=') { + errorAt( +"A regular expression literal can be confused with '/='.", line, from); + } + if (prereg) { + depth = 0; + captures = 0; + l = 0; + for (;;) { + b = true; + c = s.charAt(l); + l += 1; + switch (c) { + case '': + errorAt("Unclosed regular expression.", + line, from); + return; + case '/': + if (depth > 0) { + warningAt("Unescaped '{a}'.", + line, from + l, '/'); + } + c = s.substr(0, l - 1); + q = { + g: true, + i: true, + m: true + }; + while (q[s.charAt(l)] === true) { + q[s.charAt(l)] = false; + l += 1; + } + character += l; + s = s.substr(l); + q = s.charAt(0); + if (q === '/' || q === '*') { + errorAt("Confusing regular expression.", + line, from); + } + return it('(regexp)', c); + case '\\': + c = s.charAt(l); + if (c < ' ') { + warningAt( +"Unexpected control character in regular expression.", line, from + l); + } else if (c === '<') { + warningAt( +"Unexpected escaped character '{a}' in regular expression.", line, from + l, c); + } + l += 1; + break; + case '(': + depth += 1; + b = false; + if (s.charAt(l) === '?') { + l += 1; + switch (s.charAt(l)) { + case ':': + case '=': + case '!': + l += 1; + break; + default: + warningAt( +"Expected '{a}' and instead saw '{b}'.", line, from + l, ':', s.charAt(l)); + } + } else { + captures += 1; + } + break; + case '|': + b = false; + break; + case ')': + if (depth === 0) { + warningAt("Unescaped '{a}'.", + line, from + l, ')'); + } else { + depth -= 1; + } + break; + case ' ': + q = 1; + while (s.charAt(l) === ' ') { + l += 1; + q += 1; + } + if (q > 1) { + warningAt( +"Spaces are hard to count. Use {{a}}.", line, from + l, q); + } + break; + case '[': + c = s.charAt(l); + if (c === '^') { + l += 1; + if (option.regexp) { + warningAt("Insecure '{a}'.", + line, from + l, c); + } + } + q = false; + if (c === ']') { + warningAt("Empty class.", line, + from + l - 1); + q = true; + } +klass: do { + c = s.charAt(l); + l += 1; + switch (c) { + case '[': + case '^': + warningAt("Unescaped '{a}'.", + line, from + l, c); + q = true; + break; + case '-': + if (q) { + q = false; + } else { + warningAt("Unescaped '{a}'.", + line, from + l, '-'); + q = true; + } + break; + case ']': + if (!q) { + warningAt("Unescaped '{a}'.", + line, from + l - 1, '-'); + } + break klass; + case '\\': + c = s.charAt(l); + if (c < ' ') { + warningAt( +"Unexpected control character in regular expression.", line, from + l); + } else if (c === '<') { + warningAt( +"Unexpected escaped character '{a}' in regular expression.", line, from + l, c); + } + l += 1; + q = true; + break; + case '/': + warningAt("Unescaped '{a}'.", + line, from + l - 1, '/'); + q = true; + break; + case '<': + if (xmode === 'script') { + c = s.charAt(l); + if (c === '!' || c === '/') { + warningAt( +"HTML confusion in regular expression '<{a}'.", line, from + l, c); + } + } + q = true; + break; + default: + q = true; + } + } while (c); + break; + case '.': + if (option.regexp) { + warningAt("Insecure '{a}'.", line, + from + l, c); + } + break; + case ']': + case '?': + case '{': + case '}': + case '+': + case '*': + warningAt("Unescaped '{a}'.", line, + from + l, c); + break; + case '<': + if (xmode === 'script') { + c = s.charAt(l); + if (c === '!' || c === '/') { + warningAt( +"HTML confusion in regular expression '<{a}'.", line, from + l, c); + } + } + } + if (b) { + switch (s.charAt(l)) { + case '?': + case '+': + case '*': + l += 1; + if (s.charAt(l) === '?') { + l += 1; + } + break; + case '{': + l += 1; + c = s.charAt(l); + if (c < '0' || c > '9') { + warningAt( +"Expected a number and instead saw '{a}'.", line, from + l, c); + } + l += 1; + low = +c; + for (;;) { + c = s.charAt(l); + if (c < '0' || c > '9') { + break; + } + l += 1; + low = +c + (low * 10); + } + high = low; + if (c === ',') { + l += 1; + high = Infinity; + c = s.charAt(l); + if (c >= '0' && c <= '9') { + l += 1; + high = +c; + for (;;) { + c = s.charAt(l); + if (c < '0' || c > '9') { + break; + } + l += 1; + high = +c + (high * 10); + } + } + } + if (s.charAt(l) !== '}') { + warningAt( +"Expected '{a}' and instead saw '{b}'.", line, from + l, '}', c); + } else { + l += 1; + } + if (s.charAt(l) === '?') { + l += 1; + } + if (low > high) { + warningAt( +"'{a}' should not be greater than '{b}'.", line, from + l, low, high); + } + } + } + } + c = s.substr(0, l - 1); + character += l; + s = s.substr(l); + return it('(regexp)', c); + } + return it('(punctuator)', t); + + // punctuator + + case '.", line, character); + } + character += 3; + s = s.slice(i + 3); + break; + case '#': + if (xmode === 'html' || xmode === 'styleproperty') { + for (;;) { + c = s.charAt(0); + if ((c < '0' || c > '9') && + (c < 'a' || c > 'f') && + (c < 'A' || c > 'F')) { + break; + } + character += 1; + s = s.substr(1); + t += c; + } + if (t.length !== 4 && t.length !== 7) { + warningAt("Bad hex color '{a}'.", line, + from + l, t); + } + return it('(color)', t); + } + return it('(punctuator)', t); + default: + if (xmode === 'outer' && c === '&') { + character += 1; + s = s.substr(1); + for (;;) { + c = s.charAt(0); + character += 1; + s = s.substr(1); + if (c === ';') { + break; + } + if (!((c >= '0' && c <= '9') || + (c >= 'a' && c <= 'z') || + c === '#')) { + errorAt("Bad entity", line, from + l, + character); + } + } + break; + } + return it('(punctuator)', t); + } + } + } + } + }; + }()); + + + function addlabel(t, type) { + + if (option.safe && funct['(global)'] && + typeof predefined[t] !== 'boolean') { + warning('ADsafe global: ' + t + '.', token); + } else if (t === 'hasOwnProperty') { + warning("'hasOwnProperty' is a really bad name."); + } + +// Define t in the current function in the current scope. + + if (is_own(funct, t) && !funct['(global)']) { + warning(funct[t] === true ? + "'{a}' was used before it was defined." : + "'{a}' is already defined.", + nexttoken, t); + } + funct[t] = type; + if (funct['(global)']) { + global[t] = funct; + if (is_own(implied, t)) { + warning("'{a}' was used before it was defined.", nexttoken, t); + delete implied[t]; + } + } else { + scope[t] = funct; + } + } + + + function doOption() { + var b, obj, filter, o = nexttoken.value, t, v; + switch (o) { + case '*/': + error("Unbegun comment."); + break; + case '/*members': + case '/*member': + o = '/*members'; + if (!membersOnly) { + membersOnly = {}; + } + obj = membersOnly; + break; + case '/*jslint': + if (option.safe) { + warning("ADsafe restriction."); + } + obj = option; + filter = boolOptions; + break; + case '/*global': + if (option.safe) { + warning("ADsafe restriction."); + } + obj = predefined; + break; + default: + } + t = lex.token(); +loop: for (;;) { + for (;;) { + if (t.type === 'special' && t.value === '*/') { + break loop; + } + if (t.id !== '(endline)' && t.id !== ',') { + break; + } + t = lex.token(); + } + if (t.type !== '(string)' && t.type !== '(identifier)' && + o !== '/*members') { + error("Bad option.", t); + } + v = lex.token(); + if (v.id === ':') { + v = lex.token(); + if (obj === membersOnly) { + error("Expected '{a}' and instead saw '{b}'.", + t, '*/', ':'); + } + if (t.value === 'indent' && o === '/*jslint') { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.white = true; + obj.indent = b; + } else if (t.value === 'maxerr' && o === '/*jslint') { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.maxerr = b; + } else if (t.value === 'maxlen' && o === '/*jslint') { + b = +v.value; + if (typeof b !== 'number' || !isFinite(b) || b <= 0 || + Math.floor(b) !== b) { + error("Expected a small integer and instead saw '{a}'.", + v, v.value); + } + obj.maxlen = b; + } else if (v.value === 'true') { + obj[t.value] = true; + } else if (v.value === 'false') { + obj[t.value] = false; + } else { + error("Bad option value.", v); + } + t = lex.token(); + } else { + if (o === '/*jslint') { + error("Missing option value.", t); + } + obj[t.value] = false; + t = v; + } + } + if (filter) { + assume(); + } + } + + +// We need a peek function. If it has an argument, it peeks that much farther +// ahead. It is used to distinguish +// for ( var i in ... +// from +// for ( var i = ... + + function peek(p) { + var i = p || 0, j = 0, t; + + while (j <= i) { + t = lookahead[j]; + if (!t) { + t = lookahead[j] = lex.token(); + } + j += 1; + } + return t; + } + + + +// Produce the next token. It looks for programming errors. + + function advance(id, t) { + switch (token.id) { + case '(number)': + if (nexttoken.id === '.') { + warning( +"A dot following a number can be confused with a decimal point.", token); + } + break; + case '-': + if (nexttoken.id === '-' || nexttoken.id === '--') { + warning("Confusing minusses."); + } + break; + case '+': + if (nexttoken.id === '+' || nexttoken.id === '++') { + warning("Confusing plusses."); + } + break; + } + if (token.type === '(string)' || token.identifier) { + anonname = token.value; + } + + if (id && nexttoken.id !== id) { + if (t) { + if (nexttoken.id === '(end)') { + warning("Unmatched '{a}'.", t, t.id); + } else { + warning( +"Expected '{a}' to match '{b}' from line {c} and instead saw '{d}'.", + nexttoken, id, t.id, t.line, nexttoken.value); + } + } else if (nexttoken.type !== '(identifier)' || + nexttoken.value !== id) { + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, id, nexttoken.value); + } + } + prevtoken = token; + token = nexttoken; + for (;;) { + nexttoken = lookahead.shift() || lex.token(); + if (nexttoken.id === '(end)' || nexttoken.id === '(error)') { + return; + } + if (nexttoken.type === 'special') { + doOption(); + } else { + if (nexttoken.id !== '(endline)') { + break; + } + } + } + } + + +// This is the heart of JSLINT, the Pratt parser. In addition to parsing, it +// is looking for ad hoc lint patterns. We add to Pratt's model .fud, which is +// like nud except that it is only used on the first token of a statement. +// Having .fud makes it much easier to define JavaScript. I retained Pratt's +// nomenclature. + +// .nud Null denotation +// .fud First null denotation +// .led Left denotation +// lbp Left binding power +// rbp Right binding power + +// They are key to the parsing method called Top Down Operator Precedence. + + function parse(rbp, initial) { + var left; + if (nexttoken.id === '(end)') { + error("Unexpected early end of program.", token); + } + advance(); + if (option.safe && typeof predefined[token.value] === 'boolean' && + (nexttoken.id !== '(' && nexttoken.id !== '.')) { + warning('ADsafe violation.', token); + } + if (initial) { + anonname = 'anonymous'; + funct['(verb)'] = token.value; + } + if (initial === true && token.fud) { + left = token.fud(); + } else { + if (token.nud) { + left = token.nud(); + } else { + if (nexttoken.type === '(number)' && token.id === '.') { + warning( +"A leading decimal point can be confused with a dot: '.{a}'.", + token, nexttoken.value); + advance(); + return token; + } else { + error("Expected an identifier and instead saw '{a}'.", + token, token.id); + } + } + while (rbp < nexttoken.lbp) { + advance(); + if (token.led) { + left = token.led(left); + } else { + error("Expected an operator and instead saw '{a}'.", + token, token.id); + } + } + } + return left; + } + + +// Functions for conformance of style. + + function adjacent(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white || xmode === 'styleproperty' || xmode === 'style') { + if (left.character !== right.from && left.line === right.line) { + warning("Unexpected space after '{a}'.", right, left.value); + } + } + } + + function nobreak(left, right) { + left = left || token; + right = right || nexttoken; + if (left.character !== right.from || left.line !== right.line) { + warning("Unexpected space before '{a}'.", right, right.value); + } + } + + function nospace(left, right) { + left = left || token; + right = right || nexttoken; + if (option.white && !left.comment) { + if (left.line === right.line) { + adjacent(left, right); + } + } + } + + + function nonadjacent(left, right) { + if (option.white) { + left = left || token; + right = right || nexttoken; + if (left.line === right.line && left.character === right.from) { + warning("Missing space after '{a}'.", + nexttoken, left.value); + } + } + } + + function nobreaknonadjacent(left, right) { + left = left || token; + right = right || nexttoken; + if (!option.laxbreak && left.line !== right.line) { + warning("Bad line breaking before '{a}'.", right, right.id); + } else if (option.white) { + left = left || token; + right = right || nexttoken; + if (left.character === right.from) { + warning("Missing space after '{a}'.", + nexttoken, left.value); + } + } + } + + function indentation(bias) { + var i; + if (option.white && nexttoken.id !== '(end)') { + i = indent + (bias || 0); + if (nexttoken.from !== i) { + warning( +"Expected '{a}' to have an indentation at {b} instead at {c}.", + nexttoken, nexttoken.value, i, nexttoken.from); + } + } + } + + function nolinebreak(t) { + t = t || token; + if (t.line !== nexttoken.line) { + warning("Line breaking error '{a}'.", t, t.value); + } + } + + + function comma() { + if (token.line !== nexttoken.line) { + if (!option.laxbreak) { + warning("Bad line breaking before '{a}'.", token, nexttoken.id); + } + } else if (token.character !== nexttoken.from && option.white) { + warning("Unexpected space after '{a}'.", nexttoken, token.value); + } + advance(','); + nonadjacent(token, nexttoken); + } + + +// Functional constructors for making the symbols that will be inherited by +// tokens. + + function symbol(s, p) { + var x = syntax[s]; + if (!x || typeof x !== 'object') { + syntax[s] = x = { + id: s, + lbp: p, + value: s + }; + } + return x; + } + + + function delim(s) { + return symbol(s, 0); + } + + + function stmt(s, f) { + var x = delim(s); + x.identifier = x.reserved = true; + x.fud = f; + return x; + } + + + function blockstmt(s, f) { + var x = stmt(s, f); + x.block = true; + return x; + } + + + function reserveName(x) { + var c = x.id.charAt(0); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + x.identifier = x.reserved = true; + } + return x; + } + + + function prefix(s, f) { + var x = symbol(s, 150); + reserveName(x); + x.nud = (typeof f === 'function') ? f : function () { + this.right = parse(150); + this.arity = 'unary'; + if (this.id === '++' || this.id === '--') { + if (option.plusplus) { + warning("Unexpected use of '{a}'.", this, this.id); + } else if ((!this.right.identifier || this.right.reserved) && + this.right.id !== '.' && this.right.id !== '[') { + warning("Bad operand.", this); + } + } + return this; + }; + return x; + } + + + function type(s, f) { + var x = delim(s); + x.type = s; + x.nud = f; + return x; + } + + + function reserve(s, f) { + var x = type(s, f); + x.identifier = x.reserved = true; + return x; + } + + + function reservevar(s, v) { + return reserve(s, function () { + if (typeof v === 'function') { + v(this); + } + return this; + }); + } + + + function infix(s, f, p, w) { + var x = symbol(s, p); + reserveName(x); + x.led = function (left) { + if (!w) { + nobreaknonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + } + if (typeof f === 'function') { + return f(left, this); + } else { + this.left = left; + this.right = parse(p); + return this; + } + }; + return x; + } + + + function relation(s, f) { + var x = symbol(s, 100); + x.led = function (left) { + nobreaknonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + var right = parse(100); + if ((left && left.id === 'NaN') || (right && right.id === 'NaN')) { + warning("Use the isNaN function to compare with NaN.", this); + } else if (f) { + f.apply(this, [left, right]); + } + if (left.id === '!') { + warning("Confusing use of '{a}'.", left, '!'); + } + if (right.id === '!') { + warning("Confusing use of '{a}'.", left, '!'); + } + this.left = left; + this.right = right; + return this; + }; + return x; + } + + + function isPoorRelation(node) { + return node && + ((node.type === '(number)' && +node.value === 0) || + (node.type === '(string)' && node.value === '') || + node.type === 'true' || + node.type === 'false' || + node.type === 'undefined' || + node.type === 'null'); + } + + + function assignop(s, f) { + symbol(s, 20).exps = true; + return infix(s, function (left, that) { + var l; + that.left = left; + if (predefined[left.value] === false && + scope[left.value]['(global)'] === true) { + warning("Read only.", left); + } else if (left['function']) { + warning("'{a}' is a function.", left, left.value); + } + if (option.safe) { + l = left; + do { + if (typeof predefined[l.value] === 'boolean') { + warning('ADsafe violation.', l); + } + l = l.left; + } while (l); + } + if (left) { + if (left.id === '.' || left.id === '[') { + if (!left.left || left.left.value === 'arguments') { + warning('Bad assignment.', that); + } + that.right = parse(19); + return that; + } else if (left.identifier && !left.reserved) { + if (funct[left.value] === 'exception') { + warning("Do not assign to the exception parameter.", left); + } + that.right = parse(19); + return that; + } + if (left === syntax['function']) { + warning( +"Expected an identifier in an assignment and instead saw a function invocation.", + token); + } + } + error("Bad assignment.", that); + }, 20); + } + + function bitwise(s, f, p) { + var x = symbol(s, p); + reserveName(x); + x.led = (typeof f === 'function') ? f : function (left) { + if (option.bitwise) { + warning("Unexpected use of '{a}'.", this, this.id); + } + this.left = left; + this.right = parse(p); + return this; + }; + return x; + } + + function bitwiseassignop(s) { + symbol(s, 20).exps = true; + return infix(s, function (left, that) { + if (option.bitwise) { + warning("Unexpected use of '{a}'.", that, that.id); + } + nonadjacent(prevtoken, token); + nonadjacent(token, nexttoken); + if (left) { + if (left.id === '.' || left.id === '[' || + (left.identifier && !left.reserved)) { + parse(19); + return that; + } + if (left === syntax['function']) { + warning( +"Expected an identifier in an assignment, and instead saw a function invocation.", + token); + } + return that; + } + error("Bad assignment.", that); + }, 20); + } + + + function suffix(s, f) { + var x = symbol(s, 150); + x.led = function (left) { + if (option.plusplus) { + warning("Unexpected use of '{a}'.", this, this.id); + } else if ((!left.identifier || left.reserved) && + left.id !== '.' && left.id !== '[') { + warning("Bad operand.", this); + } + this.left = left; + return this; + }; + return x; + } + + + function optionalidentifier() { + if (nexttoken.identifier) { + advance(); + if (option.safe && banned[token.value]) { + warning("ADsafe violation: '{a}'.", token, token.value); + } else if (token.reserved && !option.es5) { + warning("Expected an identifier and instead saw '{a}' (a reserved word).", + token, token.id); + } + return token.value; + } + } + + + function identifier() { + var i = optionalidentifier(); + if (i) { + return i; + } + if (token.id === 'function' && nexttoken.id === '(') { + warning("Missing name in function statement."); + } else { + error("Expected an identifier and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + } + + function reachable(s) { + var i = 0, t; + if (nexttoken.id !== ';' || noreach) { + return; + } + for (;;) { + t = peek(i); + if (t.reach) { + return; + } + if (t.id !== '(endline)') { + if (t.id === 'function') { + warning( +"Inner functions should be listed at the top of the outer function.", t); + break; + } + warning("Unreachable '{a}' after '{b}'.", t, t.value, s); + break; + } + i += 1; + } + } + + + function statement(noindent) { + var i = indent, r, s = scope, t = nexttoken; + +// We don't like the empty statement. + + if (t.id === ';') { + warning("Unnecessary semicolon.", t); + advance(';'); + return; + } + +// Is this a labelled statement? + + if (t.identifier && !t.reserved && peek().id === ':') { + advance(); + advance(':'); + scope = Object.create(s); + addlabel(t.value, 'label'); + if (!nexttoken.labelled) { + warning("Label '{a}' on {b} statement.", + nexttoken, t.value, nexttoken.value); + } + if (jx.test(t.value + ':')) { + warning("Label '{a}' looks like a javascript url.", + t, t.value); + } + nexttoken.label = t.value; + t = nexttoken; + } + +// Parse the statement. + + if (!noindent) { + indentation(); + } + r = parse(0, true); + +// Look for the final semicolon. + + if (!t.block) { + if (!r || !r.exps) { + warning( +"Expected an assignment or function call and instead saw an expression.", + token); + } else if (r.id === '(' && r.left.id === 'new') { + warning("Do not use 'new' for side effects."); + } + if (nexttoken.id !== ';') { + warningAt("Missing semicolon.", token.line, + token.from + token.value.length); + } else { + adjacent(token, nexttoken); + advance(';'); + nonadjacent(token, nexttoken); + } + } + +// Restore the indentation. + + indent = i; + scope = s; + return r; + } + + + function use_strict() { + if (nexttoken.value === 'use strict') { + advance(); + advance(';'); + strict_mode = true; + option.newcap = true; + option.undef = true; + return true; + } else { + return false; + } + } + + + function statements(begin) { + var a = [], f, p; + if (begin && !use_strict() && option.strict) { + warning('Missing "use strict" statement.', nexttoken); + } + if (option.adsafe) { + switch (begin) { + case 'script': + if (!adsafe_may) { + if (nexttoken.value !== 'ADSAFE' || + peek(0).id !== '.' || + (peek(1).value !== 'id' && + peek(1).value !== 'go')) { + error('ADsafe violation: Missing ADSAFE.id or ADSAFE.go.', + nexttoken); + } + } + if (nexttoken.value === 'ADSAFE' && + peek(0).id === '.' && + peek(1).value === 'id') { + if (adsafe_may) { + error('ADsafe violation.', nexttoken); + } + advance('ADSAFE'); + advance('.'); + advance('id'); + advance('('); + if (nexttoken.value !== adsafe_id) { + error('ADsafe violation: id does not match.', nexttoken); + } + advance('(string)'); + advance(')'); + advance(';'); + adsafe_may = true; + } + break; + case 'lib': + if (nexttoken.value === 'ADSAFE') { + advance('ADSAFE'); + advance('.'); + advance('lib'); + advance('('); + advance('(string)'); + comma(); + f = parse(0); + if (f.id !== 'function') { + error('The second argument to lib must be a function.', f); + } + p = f.funct['(params)']; + p = p && p.join(', '); + if (p && p !== 'lib') { + error("Expected '{a}' and instead saw '{b}'.", + f, '(lib)', '(' + p + ')'); + } + advance(')'); + advance(';'); + return a; + } else { + error("ADsafe lib violation."); + } + } + } + while (!nexttoken.reach && nexttoken.id !== '(end)') { + if (nexttoken.id === ';') { + warning("Unnecessary semicolon."); + advance(';'); + } else { + a.push(statement()); + } + } + return a; + } + + + function block(f) { + var a, b = inblock, old_indent = indent, s = scope, t; + inblock = f; + scope = Object.create(scope); + nonadjacent(token, nexttoken); + t = nexttoken; + if (nexttoken.id === '{') { + advance('{'); + if (nexttoken.id !== '}' || token.line !== nexttoken.line) { + indent += option.indent; + while (!f && nexttoken.from > indent) { + indent += option.indent; + } + if (!f) { + use_strict(); + } + a = statements(); + indent -= option.indent; + indentation(); + } + advance('}', t); + indent = old_indent; + } else { + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, '{', nexttoken.value); + noreach = true; + a = [statement()]; + noreach = false; + } + funct['(verb)'] = null; + scope = s; + inblock = b; + return a; + } + + + function countMember(m) { + if (membersOnly && typeof membersOnly[m] !== 'boolean') { + warning("Unexpected /*member '{a}'.", token, m); + } + if (typeof member[m] === 'number') { + member[m] += 1; + } else { + member[m] = 1; + } + } + + + function note_implied(token) { + var name = token.value, line = token.line, a = implied[name]; + if (typeof a === 'function') { + a = false; + } + if (!a) { + a = [line]; + implied[name] = a; + } else if (a[a.length - 1] !== line) { + a.push(line); + } + } + +// CSS parsing. + + + function cssName() { + if (nexttoken.identifier) { + advance(); + return true; + } + } + + function cssNumber() { + if (nexttoken.id === '-') { + advance('-'); + adjacent(); + nolinebreak(); + } + if (nexttoken.type === '(number)') { + advance('(number)'); + return true; + } + } + + function cssString() { + if (nexttoken.type === '(string)') { + advance(); + return true; + } + } + + function cssColor() { + var i, number, value; + if (nexttoken.identifier) { + value = nexttoken.value; + if (value === 'rgb' || value === 'rgba') { + advance(); + advance('('); + for (i = 0; i < 3; i += 1) { + if (i) { + advance(','); + } + number = nexttoken.value; + if (nexttoken.type !== '(number)' || number < 0) { + warning("Expected a positive number and instead saw '{a}'", + nexttoken, number); + advance(); + } else { + advance(); + if (nexttoken.id === '%') { + advance('%'); + if (number > 100) { + warning("Expected a percentage and instead saw '{a}'", + token, number); + } + } else { + if (number > 255) { + warning("Expected a small number and instead saw '{a}'", + token, number); + } + } + } + } + if (value === 'rgba') { + advance(','); + number = +nexttoken.value; + if (nexttoken.type !== '(number)' || number < 0 || number > 1) { + warning("Expected a number between 0 and 1 and instead saw '{a}'", + nexttoken, number); + } + advance(); + if (nexttoken.id === '%') { + warning("Unexpected '%'."); + advance('%'); + } + } + advance(')'); + return true; + } else if (cssColorData[nexttoken.value] === true) { + advance(); + return true; + } + } else if (nexttoken.type === '(color)') { + advance(); + return true; + } + return false; + } + + function cssLength() { + if (nexttoken.id === '-') { + advance('-'); + adjacent(); + nolinebreak(); + } + if (nexttoken.type === '(number)') { + advance(); + if (nexttoken.type !== '(string)' && + cssLengthData[nexttoken.value] === true) { + adjacent(); + advance(); + } else if (+token.value !== 0) { + warning("Expected a linear unit and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + return true; + } + return false; + } + + function cssLineHeight() { + if (nexttoken.id === '-') { + advance('-'); + adjacent(); + } + if (nexttoken.type === '(number)') { + advance(); + if (nexttoken.type !== '(string)' && + cssLengthData[nexttoken.value] === true) { + adjacent(); + advance(); + } + return true; + } + return false; + } + + function cssWidth() { + if (nexttoken.identifier) { + switch (nexttoken.value) { + case 'thin': + case 'medium': + case 'thick': + advance(); + return true; + } + } else { + return cssLength(); + } + } + + function cssMargin() { + if (nexttoken.identifier) { + if (nexttoken.value === 'auto') { + advance(); + return true; + } + } else { + return cssLength(); + } + } + + function cssAttr() { + if (nexttoken.identifier && nexttoken.value === 'attr') { + advance(); + advance('('); + if (!nexttoken.identifier) { + warning("Expected a name and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + advance(')'); + return true; + } + return false; + } + + function cssCommaList() { + while (nexttoken.id !== ';') { + if (!cssName() && !cssString()) { + warning("Expected a name and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + if (nexttoken.id !== ',') { + return true; + } + comma(); + } + } + + function cssCounter() { + if (nexttoken.identifier && nexttoken.value === 'counter') { + advance(); + advance('('); + if (!nexttoken.identifier) { + } + advance(); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.type !== '(string)') { + warning("Expected a string and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + } + advance(')'); + return true; + } + if (nexttoken.identifier && nexttoken.value === 'counters') { + advance(); + advance('('); + if (!nexttoken.identifier) { + warning("Expected a name and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.type !== '(string)') { + warning("Expected a string and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + } + if (nexttoken.id === ',') { + comma(); + if (nexttoken.type !== '(string)') { + warning("Expected a string and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + } + advance(')'); + return true; + } + return false; + } + + + function cssShape() { + var i; + if (nexttoken.identifier && nexttoken.value === 'rect') { + advance(); + advance('('); + for (i = 0; i < 4; i += 1) { + if (!cssLength()) { + warning("Expected a number and instead saw '{a}'.", + nexttoken, nexttoken.value); + break; + } + } + advance(')'); + return true; + } + return false; + } + + function cssUrl() { + var c, url; + if (nexttoken.identifier && nexttoken.value === 'url') { + nexttoken = lex.range('(', ')'); + url = nexttoken.value; + c = url.charAt(0); + if (c === '"' || c === '\'') { + if (url.slice(-1) !== c) { + warning("Bad url string."); + } else { + url = url.slice(1, -1); + if (url.indexOf(c) >= 0) { + warning("Bad url string."); + } + } + } + if (!url) { + warning("Missing url."); + } + advance(); + if (option.safe && ux.test(url)) { + error("ADsafe URL violation."); + } + urls.push(url); + return true; + } + return false; + } + + cssAny = [cssUrl, function () { + for (;;) { + if (nexttoken.identifier) { + switch (nexttoken.value.toLowerCase()) { + case 'url': + cssUrl(); + break; + case 'expression': + warning("Unexpected expression '{a}'.", + nexttoken, nexttoken.value); + advance(); + break; + default: + advance(); + } + } else { + if (nexttoken.id === ';' || nexttoken.id === '!' || + nexttoken.id === '(end)' || nexttoken.id === '}') { + return true; + } + advance(); + } + } + }]; + + cssBorderStyle = [ + 'none', 'hidden', 'dotted', 'dashed', 'solid', 'double', 'ridge', + 'inset', 'outset' + ]; + + cssBreak = [ + 'auto', 'always', 'avoid', 'left', 'right' + ]; + + cssOverflow = [ + 'auto', 'hidden', 'scroll', 'visible' + ]; + + cssAttributeData = { + background: [ + true, 'background-attachment', 'background-color', + 'background-image', 'background-position', 'background-repeat' + ], + 'background-attachment': ['scroll', 'fixed'], + 'background-color': ['transparent', cssColor], + 'background-image': ['none', cssUrl], + 'background-position': [ + 2, [cssLength, 'top', 'bottom', 'left', 'right', 'center'] + ], + 'background-repeat': [ + 'repeat', 'repeat-x', 'repeat-y', 'no-repeat' + ], + 'border': [true, 'border-color', 'border-style', 'border-width'], + 'border-bottom': [ + true, 'border-bottom-color', 'border-bottom-style', + 'border-bottom-width' + ], + 'border-bottom-color': cssColor, + 'border-bottom-style': cssBorderStyle, + 'border-bottom-width': cssWidth, + 'border-collapse': ['collapse', 'separate'], + 'border-color': ['transparent', 4, cssColor], + 'border-left': [ + true, 'border-left-color', 'border-left-style', 'border-left-width' + ], + 'border-left-color': cssColor, + 'border-left-style': cssBorderStyle, + 'border-left-width': cssWidth, + 'border-right': [ + true, 'border-right-color', 'border-right-style', + 'border-right-width' + ], + 'border-right-color': cssColor, + 'border-right-style': cssBorderStyle, + 'border-right-width': cssWidth, + 'border-spacing': [2, cssLength], + 'border-style': [4, cssBorderStyle], + 'border-top': [ + true, 'border-top-color', 'border-top-style', 'border-top-width' + ], + 'border-top-color': cssColor, + 'border-top-style': cssBorderStyle, + 'border-top-width': cssWidth, + 'border-width': [4, cssWidth], + bottom: [cssLength, 'auto'], + 'caption-side' : ['bottom', 'left', 'right', 'top'], + clear: ['both', 'left', 'none', 'right'], + clip: [cssShape, 'auto'], + color: cssColor, + content: [ + 'open-quote', 'close-quote', 'no-open-quote', 'no-close-quote', + cssString, cssUrl, cssCounter, cssAttr + ], + 'counter-increment': [ + cssName, 'none' + ], + 'counter-reset': [ + cssName, 'none' + ], + cursor: [ + cssUrl, 'auto', 'crosshair', 'default', 'e-resize', 'help', 'move', + 'n-resize', 'ne-resize', 'nw-resize', 'pointer', 's-resize', + 'se-resize', 'sw-resize', 'w-resize', 'text', 'wait' + ], + direction: ['ltr', 'rtl'], + display: [ + 'block', 'compact', 'inline', 'inline-block', 'inline-table', + 'list-item', 'marker', 'none', 'run-in', 'table', 'table-caption', + 'table-cell', 'table-column', 'table-column-group', + 'table-footer-group', 'table-header-group', 'table-row', + 'table-row-group' + ], + 'empty-cells': ['show', 'hide'], + 'float': ['left', 'none', 'right'], + font: [ + 'caption', 'icon', 'menu', 'message-box', 'small-caption', + 'status-bar', true, 'font-size', 'font-style', 'font-weight', + 'font-family' + ], + 'font-family': cssCommaList, + 'font-size': [ + 'xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', + 'xx-large', 'larger', 'smaller', cssLength + ], + 'font-size-adjust': ['none', cssNumber], + 'font-stretch': [ + 'normal', 'wider', 'narrower', 'ultra-condensed', + 'extra-condensed', 'condensed', 'semi-condensed', + 'semi-expanded', 'expanded', 'extra-expanded' + ], + 'font-style': [ + 'normal', 'italic', 'oblique' + ], + 'font-variant': [ + 'normal', 'small-caps' + ], + 'font-weight': [ + 'normal', 'bold', 'bolder', 'lighter', cssNumber + ], + height: [cssLength, 'auto'], + left: [cssLength, 'auto'], + 'letter-spacing': ['normal', cssLength], + 'line-height': ['normal', cssLineHeight], + 'list-style': [ + true, 'list-style-image', 'list-style-position', 'list-style-type' + ], + 'list-style-image': ['none', cssUrl], + 'list-style-position': ['inside', 'outside'], + 'list-style-type': [ + 'circle', 'disc', 'square', 'decimal', 'decimal-leading-zero', + 'lower-roman', 'upper-roman', 'lower-greek', 'lower-alpha', + 'lower-latin', 'upper-alpha', 'upper-latin', 'hebrew', 'katakana', + 'hiragana-iroha', 'katakana-oroha', 'none' + ], + margin: [4, cssMargin], + 'margin-bottom': cssMargin, + 'margin-left': cssMargin, + 'margin-right': cssMargin, + 'margin-top': cssMargin, + 'marker-offset': [cssLength, 'auto'], + 'max-height': [cssLength, 'none'], + 'max-width': [cssLength, 'none'], + 'min-height': cssLength, + 'min-width': cssLength, + opacity: cssNumber, + outline: [true, 'outline-color', 'outline-style', 'outline-width'], + 'outline-color': ['invert', cssColor], + 'outline-style': [ + 'dashed', 'dotted', 'double', 'groove', 'inset', 'none', + 'outset', 'ridge', 'solid' + ], + 'outline-width': cssWidth, + overflow: cssOverflow, + 'overflow-x': cssOverflow, + 'overflow-y': cssOverflow, + padding: [4, cssLength], + 'padding-bottom': cssLength, + 'padding-left': cssLength, + 'padding-right': cssLength, + 'padding-top': cssLength, + 'page-break-after': cssBreak, + 'page-break-before': cssBreak, + position: ['absolute', 'fixed', 'relative', 'static'], + quotes: [8, cssString], + right: [cssLength, 'auto'], + 'table-layout': ['auto', 'fixed'], + 'text-align': ['center', 'justify', 'left', 'right'], + 'text-decoration': [ + 'none', 'underline', 'overline', 'line-through', 'blink' + ], + 'text-indent': cssLength, + 'text-shadow': ['none', 4, [cssColor, cssLength]], + 'text-transform': ['capitalize', 'uppercase', 'lowercase', 'none'], + top: [cssLength, 'auto'], + 'unicode-bidi': ['normal', 'embed', 'bidi-override'], + 'vertical-align': [ + 'baseline', 'bottom', 'sub', 'super', 'top', 'text-top', 'middle', + 'text-bottom', cssLength + ], + visibility: ['visible', 'hidden', 'collapse'], + 'white-space': [ + 'normal', 'nowrap', 'pre', 'pre-line', 'pre-wrap', 'inherit' + ], + width: [cssLength, 'auto'], + 'word-spacing': ['normal', cssLength], + 'word-wrap': ['break-word', 'normal'], + 'z-index': ['auto', cssNumber] + }; + + function styleAttribute() { + var v; + while (nexttoken.id === '*' || nexttoken.id === '#' || + nexttoken.value === '_') { + if (!option.css) { + warning("Unexpected '{a}'.", nexttoken, nexttoken.value); + } + advance(); + } + if (nexttoken.id === '-') { + if (!option.css) { + warning("Unexpected '{a}'.", nexttoken, nexttoken.value); + } + advance('-'); + if (!nexttoken.identifier) { + warning( +"Expected a non-standard style attribute and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + return cssAny; + } else { + if (!nexttoken.identifier) { + warning("Excepted a style attribute, and instead saw '{a}'.", + nexttoken, nexttoken.value); + } else { + if (is_own(cssAttributeData, nexttoken.value)) { + v = cssAttributeData[nexttoken.value]; + } else { + v = cssAny; + if (!option.css) { + warning("Unrecognized style attribute '{a}'.", + nexttoken, nexttoken.value); + } + } + } + advance(); + return v; + } + } + + function styleValue(v) { + var i = 0, + n, + once, + match, + round, + start = 0, + vi; + switch (typeof v) { + case 'function': + return v(); + case 'string': + if (nexttoken.identifier && nexttoken.value === v) { + advance(); + return true; + } + return false; + } + for (;;) { + if (i >= v.length) { + return false; + } + vi = v[i]; + i += 1; + if (vi === true) { + break; + } else if (typeof vi === 'number') { + n = vi; + vi = v[i]; + i += 1; + } else { + n = 1; + } + match = false; + while (n > 0) { + if (styleValue(vi)) { + match = true; + n -= 1; + } else { + break; + } + } + if (match) { + return true; + } + } + start = i; + once = []; + for (;;) { + round = false; + for (i = start; i < v.length; i += 1) { + if (!once[i]) { + if (styleValue(cssAttributeData[v[i]])) { + match = true; + round = true; + once[i] = true; + break; + } + } + } + if (!round) { + return match; + } + } + } + + function styleChild() { + if (nexttoken.id === '(number)') { + advance(); + if (nexttoken.value === 'n' && nexttoken.identifier) { + adjacent(); + advance(); + if (nexttoken.id === '+') { + adjacent(); + advance('+'); + adjacent(); + advance('(number)'); + } + } + return; + } else { + switch (nexttoken.value) { + case 'odd': + case 'even': + if (nexttoken.identifier) { + advance(); + return; + } + } + } + warning("Unexpected token '{a}'.", nexttoken, nexttoken.value); + } + + function substyle() { + var v; + for (;;) { + if (nexttoken.id === '}' || nexttoken.id === '(end)' || + xquote && nexttoken.id === xquote) { + return; + } + while (nexttoken.id === ';') { + warning("Misplaced ';'."); + advance(';'); + } + v = styleAttribute(); + advance(':'); + if (nexttoken.identifier && nexttoken.value === 'inherit') { + advance(); + } else { + if (!styleValue(v)) { + warning("Unexpected token '{a}'.", nexttoken, + nexttoken.value); + advance(); + } + } + if (nexttoken.id === '!') { + advance('!'); + adjacent(); + if (nexttoken.identifier && nexttoken.value === 'important') { + advance(); + } else { + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, 'important', nexttoken.value); + } + } + if (nexttoken.id === '}' || nexttoken.id === xquote) { + warning("Missing '{a}'.", nexttoken, ';'); + } else { + advance(';'); + } + } + } + + function styleSelector() { + if (nexttoken.identifier) { + if (!is_own(htmltag, nexttoken.value)) { + warning("Expected a tagName, and instead saw {a}.", + nexttoken, nexttoken.value); + } + advance(); + } else { + switch (nexttoken.id) { + case '>': + case '+': + advance(); + styleSelector(); + break; + case ':': + advance(':'); + switch (nexttoken.value) { + case 'active': + case 'after': + case 'before': + case 'checked': + case 'disabled': + case 'empty': + case 'enabled': + case 'first-child': + case 'first-letter': + case 'first-line': + case 'first-of-type': + case 'focus': + case 'hover': + case 'last-of-type': + case 'link': + case 'only-of-type': + case 'root': + case 'target': + case 'visited': + advance(); + break; + case 'lang': + advance(); + advance('('); + if (!nexttoken.identifier) { + warning("Expected a lang code, and instead saw :{a}.", + nexttoken, nexttoken.value); + } + advance(')'); + break; + case 'nth-child': + case 'nth-last-child': + case 'nth-last-of-type': + case 'nth-of-type': + advance(); + advance('('); + styleChild(); + advance(')'); + break; + case 'not': + advance(); + advance('('); + if (nexttoken.id === ':' && peek(0).value === 'not') { + warning("Nested not."); + } + styleSelector(); + advance(')'); + break; + default: + warning("Expected a pseudo, and instead saw :{a}.", + nexttoken, nexttoken.value); + } + break; + case '#': + advance('#'); + if (!nexttoken.identifier) { + warning("Expected an id, and instead saw #{a}.", + nexttoken, nexttoken.value); + } + advance(); + break; + case '*': + advance('*'); + break; + case '.': + advance('.'); + if (!nexttoken.identifier) { + warning("Expected a class, and instead saw #.{a}.", + nexttoken, nexttoken.value); + } + advance(); + break; + case '[': + advance('['); + if (!nexttoken.identifier) { + warning("Expected an attribute, and instead saw [{a}].", + nexttoken, nexttoken.value); + } + advance(); + if (nexttoken.id === '=' || nexttoken.value === '~=' || + nexttoken.value === '$=' || + nexttoken.value === '|=' || + nexttoken.id === '*=' || + nexttoken.id === '^=') { + advance(); + if (nexttoken.type !== '(string)') { + warning("Expected a string, and instead saw {a}.", + nexttoken, nexttoken.value); + } + advance(); + } + advance(']'); + break; + default: + error("Expected a CSS selector, and instead saw {a}.", + nexttoken, nexttoken.value); + } + } + } + + function stylePattern() { + var name; + if (nexttoken.id === '{') { + warning("Expected a style pattern, and instead saw '{a}'.", nexttoken, + nexttoken.id); + } else if (nexttoken.id === '@') { + advance('@'); + name = nexttoken.value; + if (nexttoken.identifier && atrule[name] === true) { + advance(); + return name; + } + warning("Expected an at-rule, and instead saw @{a}.", nexttoken, name); + } + for (;;) { + styleSelector(); + if (nexttoken.id === ' fragments and .js files.", token); + } + if (option.fragment) { + if (n !== 'div') { + error("ADsafe violation: Wrap the widget in a div.", token); + } + } else { + error("Use the fragment option.", token); + } + } + option.browser = true; + assume(); + } + + function doAttribute(n, a, v) { + var u, x; + if (a === 'id') { + u = typeof v === 'string' ? v.toUpperCase() : ''; + if (ids[u] === true) { + warning("Duplicate id='{a}'.", nexttoken, v); + } + if (!/^[A-Za-z][A-Za-z0-9._:\-]*$/.test(v)) { + warning("Bad id: '{a}'.", nexttoken, v); + } else if (option.adsafe) { + if (adsafe_id) { + if (v.slice(0, adsafe_id.length) !== adsafe_id) { + warning("ADsafe violation: An id must have a '{a}' prefix", + nexttoken, adsafe_id); + } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) { + warning("ADSAFE violation: bad id."); + } + } else { + adsafe_id = v; + if (!/^[A-Z]+_$/.test(v)) { + warning("ADSAFE violation: bad id."); + } + } + } + x = v.search(dx); + if (x >= 0) { + warning("Unexpected character '{a}' in {b}.", token, v.charAt(x), a); + } + ids[u] = true; + } else if (a === 'class' || a === 'type' || a === 'name') { + x = v.search(qx); + if (x >= 0) { + warning("Unexpected character '{a}' in {b}.", token, v.charAt(x), a); + } + ids[u] = true; + } else if (a === 'href' || a === 'background' || + a === 'content' || a === 'data' || + a.indexOf('src') >= 0 || a.indexOf('url') >= 0) { + if (option.safe && ux.test(v)) { + error("ADsafe URL violation."); + } + urls.push(v); + } else if (a === 'for') { + if (option.adsafe) { + if (adsafe_id) { + if (v.slice(0, adsafe_id.length) !== adsafe_id) { + warning("ADsafe violation: An id must have a '{a}' prefix", + nexttoken, adsafe_id); + } else if (!/^[A-Z]+_[A-Z]+$/.test(v)) { + warning("ADSAFE violation: bad id."); + } + } else { + warning("ADSAFE violation: bad id."); + } + } + } else if (a === 'name') { + if (option.adsafe && v.indexOf('_') >= 0) { + warning("ADsafe name violation."); + } + } + } + + function doTag(n, a) { + var i, t = htmltag[n], x; + src = false; + if (!t) { + error("Unrecognized tag '<{a}>'.", + nexttoken, + n === n.toLowerCase() ? n : + n + ' (capitalization error)'); + } + if (stack.length > 0) { + if (n === 'html') { + error("Too many tags.", token); + } + x = t.parent; + if (x) { + if (x.indexOf(' ' + stack[stack.length - 1].name + ' ') < 0) { + error("A '<{a}>' must be within '<{b}>'.", + token, n, x); + } + } else if (!option.adsafe && !option.fragment) { + i = stack.length; + do { + if (i <= 0) { + error("A '<{a}>' must be within '<{b}>'.", + token, n, 'body'); + } + i -= 1; + } while (stack[i].name !== 'body'); + } + } + switch (n) { + case 'div': + if (option.adsafe && stack.length === 1 && !adsafe_id) { + warning("ADSAFE violation: missing ID_."); + } + break; + case 'script': + xmode = 'script'; + advance('>'); + indent = nexttoken.from; + if (a.lang) { + warning("lang is deprecated.", token); + } + if (option.adsafe && stack.length !== 1) { + warning("ADsafe script placement violation.", token); + } + if (a.src) { + if (option.adsafe && (!adsafe_may || !approved[a.src])) { + warning("ADsafe unapproved script source.", token); + } + if (a.type) { + warning("type is unnecessary.", token); + } + } else { + if (adsafe_went) { + error("ADsafe script violation.", token); + } + statements('script'); + } + xmode = 'html'; + advance(''); + styles(); + xmode = 'html'; + advance(''; + } + + function html() { + var a, attributes, e, n, q, t, v, w = option.white, wmode; + xmode = 'html'; + xquote = ''; + stack = null; + for (;;) { + switch (nexttoken.value) { + case '<': + xmode = 'html'; + advance('<'); + attributes = {}; + t = nexttoken; + if (!t.identifier) { + warning("Bad identifier {a}.", t, t.value); + } + n = t.value; + if (option.cap) { + n = n.toLowerCase(); + } + t.name = n; + advance(); + if (!stack) { + stack = []; + doBegin(n); + } + v = htmltag[n]; + if (typeof v !== 'object') { + error("Unrecognized tag '<{a}>'.", t, n); + } + e = v.empty; + t.type = n; + for (;;) { + if (nexttoken.id === '/') { + advance('/'); + if (nexttoken.id !== '>') { + warning("Expected '{a}' and instead saw '{b}'.", + nexttoken, '>', nexttoken.value); + } + break; + } + if (nexttoken.id && nexttoken.id.substr(0, 1) === '>') { + break; + } + if (!nexttoken.identifier) { + if (nexttoken.id === '(end)' || nexttoken.id === '(error)') { + error("Missing '>'.", nexttoken); + } + warning("Bad identifier."); + } + option.white = true; + nonadjacent(token, nexttoken); + a = nexttoken.value; + option.white = w; + advance(); + if (!option.cap && a !== a.toLowerCase()) { + warning("Attribute '{a}' not all lower case.", nexttoken, a); + } + a = a.toLowerCase(); + xquote = ''; + if (is_own(attributes, a)) { + warning("Attribute '{a}' repeated.", nexttoken, a); + } + if (a.slice(0, 2) === 'on') { + if (!option.on) { + warning("Avoid HTML event handlers."); + } + xmode = 'scriptstring'; + advance('='); + q = nexttoken.id; + if (q !== '"' && q !== "'") { + error("Missing quote."); + } + xquote = q; + wmode = option.white; + option.white = false; + advance(q); + statements('on'); + option.white = wmode; + if (nexttoken.id !== q) { + error("Missing close quote on script attribute."); + } + xmode = 'html'; + xquote = ''; + advance(q); + v = false; + } else if (a === 'style') { + xmode = 'scriptstring'; + advance('='); + q = nexttoken.id; + if (q !== '"' && q !== "'") { + error("Missing quote."); + } + xmode = 'styleproperty'; + xquote = q; + advance(q); + substyle(); + xmode = 'html'; + xquote = ''; + advance(q); + v = false; + } else { + if (nexttoken.id === '=') { + advance('='); + v = nexttoken.value; + if (!nexttoken.identifier && + nexttoken.id !== '"' && + nexttoken.id !== '\'' && + nexttoken.type !== '(string)' && + nexttoken.type !== '(number)' && + nexttoken.type !== '(color)') { + warning("Expected an attribute value and instead saw '{a}'.", token, a); + } + advance(); + } else { + v = true; + } + } + attributes[a] = v; + doAttribute(n, a, v); + } + doTag(n, attributes); + if (!e) { + stack.push(t); + } + xmode = 'outer'; + advance('>'); + break; + case '') { + error("Missing '{a}'.", nexttoken, '>'); + } + xmode = 'outer'; + advance('>'); + break; + case '' || nexttoken.id === '(end)') { + break; + } + if (nexttoken.value.indexOf('--') >= 0) { + error("Unexpected --."); + } + if (nexttoken.value.indexOf('<') >= 0) { + error("Unexpected <."); + } + if (nexttoken.value.indexOf('>') >= 0) { + error("Unexpected >."); + } + } + xmode = 'outer'; + advance('>'); + break; + case '(end)': + return; + default: + if (nexttoken.id === '(end)') { + error("Missing '{a}'.", nexttoken, + ''); + } else { + advance(); + } + } + if (stack && stack.length === 0 && (option.adsafe || + !option.fragment || nexttoken.id === '(end)')) { + break; + } + } + if (nexttoken.id !== '(end)') { + error("Unexpected material after the end."); + } + } + + +// Build the syntax table by declaring the syntactic elements of the language. + + type('(number)', function () { + return this; + }); + type('(string)', function () { + return this; + }); + + syntax['(identifier)'] = { + type: '(identifier)', + lbp: 0, + identifier: true, + nud: function () { + var v = this.value, + s = scope[v], + f; + if (typeof s === 'function') { + +// Protection against accidental inheritance. + + s = undefined; + } else if (typeof s === 'boolean') { + f = funct; + funct = functions[0]; + addlabel(v, 'var'); + s = funct; + funct = f; + } + +// The name is in scope and defined in the current function. + + if (funct === s) { + +// Change 'unused' to 'var', and reject labels. + + switch (funct[v]) { + case 'unused': + funct[v] = 'var'; + break; + case 'unction': + funct[v] = 'function'; + this['function'] = true; + break; + case 'function': + this['function'] = true; + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + break; + } + +// The name is not defined in the function. If we are in the global scope, +// then we have an undefined variable. + + } else if (funct['(global)']) { + if (option.undef && predefined[v] !== 'boolean') { + warning("'{a}' is not defined.", token, v); + } + note_implied(token); + +// If the name is already defined in the current +// function, but not as outer, then there is a scope error. + + } else { + switch (funct[v]) { + case 'closure': + case 'function': + case 'var': + case 'unused': + warning("'{a}' used out of scope.", token, v); + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + break; + case 'outer': + case 'global': + break; + default: + +// If the name is defined in an outer function, make an outer entry, and if +// it was unused, make it var. + + if (s === true) { + funct[v] = true; + } else if (s === null) { + warning("'{a}' is not allowed.", token, v); + note_implied(token); + } else if (typeof s !== 'object') { + if (option.undef) { + warning("'{a}' is not defined.", token, v); + } else { + funct[v] = true; + } + note_implied(token); + } else { + switch (s[v]) { + case 'function': + case 'unction': + this['function'] = true; + s[v] = 'closure'; + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'var': + case 'unused': + s[v] = 'closure'; + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'closure': + case 'parameter': + funct[v] = s['(global)'] ? 'global' : 'outer'; + break; + case 'label': + warning("'{a}' is a statement label.", token, v); + } + } + } + } + return this; + }, + led: function () { + error("Expected an operator and instead saw '{a}'.", + nexttoken, nexttoken.value); + } + }; + + type('(regexp)', function () { + return this; + }); + + +// ECMAScript parser + + delim('(endline)'); + delim('(begin)'); + delim('(end)').reach = true; + delim(''); + delim('(error)').reach = true; + delim('}').reach = true; + delim(')'); + delim(']'); + delim('"').reach = true; + delim("'").reach = true; + delim(';'); + delim(':').reach = true; + delim(','); + delim('#'); + delim('@'); + reserve('else'); + reserve('case').reach = true; + reserve('catch'); + reserve('default').reach = true; + reserve('finally'); + reservevar('arguments', function (x) { + if (strict_mode && funct['(global)']) { + warning("Strict violation.", x); + } else if (option.safe) { + warning("ADsafe violation.", x); + } + }); + reservevar('eval', function (x) { + if (option.safe) { + warning("ADsafe violation.", x); + } + }); + reservevar('false'); + reservevar('Infinity'); + reservevar('NaN'); + reservevar('null'); + reservevar('this', function (x) { + if (strict_mode && ((funct['(statement)'] && + funct['(name)'].charAt(0) > 'Z') || funct['(global)'])) { + warning("Strict violation.", x); + } else if (option.safe) { + warning("ADsafe violation.", x); + } + }); + reservevar('true'); + reservevar('undefined'); + assignop('=', 'assign', 20); + assignop('+=', 'assignadd', 20); + assignop('-=', 'assignsub', 20); + assignop('*=', 'assignmult', 20); + assignop('/=', 'assigndiv', 20).nud = function () { + error("A regular expression literal can be confused with '/='."); + }; + assignop('%=', 'assignmod', 20); + bitwiseassignop('&=', 'assignbitand', 20); + bitwiseassignop('|=', 'assignbitor', 20); + bitwiseassignop('^=', 'assignbitxor', 20); + bitwiseassignop('<<=', 'assignshiftleft', 20); + bitwiseassignop('>>=', 'assignshiftright', 20); + bitwiseassignop('>>>=', 'assignshiftrightunsigned', 20); + infix('?', function (left, that) { + that.left = left; + that.right = parse(10); + advance(':'); + that['else'] = parse(10); + return that; + }, 30); + + infix('||', 'or', 40); + infix('&&', 'and', 50); + bitwise('|', 'bitor', 70); + bitwise('^', 'bitxor', 80); + bitwise('&', 'bitand', 90); + relation('==', function (left, right) { + if (option.eqeqeq) { + warning("Expected '{a}' and instead saw '{b}'.", + this, '===', '=='); + } else if (isPoorRelation(left)) { + warning("Use '{a}' to compare with '{b}'.", + this, '===', left.value); + } else if (isPoorRelation(right)) { + warning("Use '{a}' to compare with '{b}'.", + this, '===', right.value); + } + return this; + }); + relation('==='); + relation('!=', function (left, right) { + if (option.eqeqeq) { + warning("Expected '{a}' and instead saw '{b}'.", + this, '!==', '!='); + } else if (isPoorRelation(left)) { + warning("Use '{a}' to compare with '{b}'.", + this, '!==', left.value); + } else if (isPoorRelation(right)) { + warning("Use '{a}' to compare with '{b}'.", + this, '!==', right.value); + } + return this; + }); + relation('!=='); + relation('<'); + relation('>'); + relation('<='); + relation('>='); + bitwise('<<', 'shiftleft', 120); + bitwise('>>', 'shiftright', 120); + bitwise('>>>', 'shiftrightunsigned', 120); + infix('in', 'in', 120); + infix('instanceof', 'instanceof', 120); + infix('+', function (left, that) { + var right = parse(130); + if (left && right && left.id === '(string)' && right.id === '(string)') { + left.value += right.value; + left.character = right.character; + if (jx.test(left.value)) { + warning("JavaScript URL.", left); + } + return left; + } + that.left = left; + that.right = right; + return that; + }, 130); + prefix('+', 'num'); + prefix('+++', function () { + warning("Confusing pluses."); + this.right = parse(150); + this.arity = 'unary'; + return this; + }); + infix('+++', function (left) { + warning("Confusing pluses."); + this.left = left; + this.right = parse(130); + return this; + }, 130); + infix('-', 'sub', 130); + prefix('-', 'neg'); + prefix('---', function () { + warning("Confusing minuses."); + this.right = parse(150); + this.arity = 'unary'; + return this; + }); + infix('---', function (left) { + warning("Confusing minuses."); + this.left = left; + this.right = parse(130); + return this; + }, 130); + infix('*', 'mult', 140); + infix('/', 'div', 140); + infix('%', 'mod', 140); + + suffix('++', 'postinc'); + prefix('++', 'preinc'); + syntax['++'].exps = true; + + suffix('--', 'postdec'); + prefix('--', 'predec'); + syntax['--'].exps = true; + prefix('delete', function () { + var p = parse(0); + if (!p || (p.id !== '.' && p.id !== '[')) { + warning("Variables should not be deleted."); + } + this.first = p; + return this; + }).exps = true; + + + prefix('~', function () { + if (option.bitwise) { + warning("Unexpected '{a}'.", this, '~'); + } + parse(150); + return this; + }); + prefix('!', function () { + this.right = parse(150); + this.arity = 'unary'; + if (bang[this.right.id] === true) { + warning("Confusing use of '{a}'.", this, '!'); + } + return this; + }); + prefix('typeof', 'typeof'); + prefix('new', function () { + var c = parse(155), i; + if (c && c.id !== 'function') { + if (c.identifier) { + c['new'] = true; + switch (c.value) { + case 'Object': + warning("Use the object literal notation {}.", token); + break; + case 'Array': + if (nexttoken.id !== '(') { + warning("Use the array literal notation [].", token); + } else { + advance('('); + if (nexttoken.id === ')') { + warning("Use the array literal notation [].", token); + } else { + i = parse(0); + c.dimension = i; + if ((i.id === '(number)' && /[.+\-Ee]/.test(i.value)) || + (i.id === '-' && !i.right) || + i.id === '(string)' || i.id === '[' || + i.id === '{' || i.id === 'true' || + i.id === 'false' || + i.id === 'null' || i.id === 'undefined' || + i.id === 'Infinity') { + warning("Use the array literal notation [].", token); + } + if (nexttoken.id !== ')') { + error("Use the array literal notation [].", token); + } + } + advance(')'); + } + this.first = c; + return this; + case 'Number': + case 'String': + case 'Boolean': + case 'Math': + case 'JSON': + warning("Do not use {a} as a constructor.", token, c.value); + break; + case 'Function': + if (!option.evil) { + warning("The Function constructor is eval."); + } + break; + case 'Date': + case 'RegExp': + break; + default: + if (c.id !== 'function') { + i = c.value.substr(0, 1); + if (option.newcap && (i < 'A' || i > 'Z')) { + warning( + "A constructor name should start with an uppercase letter.", + token); + } + } + } + } else { + if (c.id !== '.' && c.id !== '[' && c.id !== '(') { + warning("Bad constructor.", token); + } + } + } else { + warning("Weird construction. Delete 'new'.", this); + } + adjacent(token, nexttoken); + if (nexttoken.id !== '(') { + warning("Missing '()' invoking a constructor."); + } + this.first = c; + return this; + }); + syntax['new'].exps = true; + + infix('.', function (left, that) { + adjacent(prevtoken, token); + nobreak(); + var m = identifier(); + if (typeof m === 'string') { + countMember(m); + } + that.left = left; + that.right = m; + if (left && left.value === 'arguments' && + (m === 'callee' || m === 'caller')) { + warning("Avoid arguments.{a}.", left, m); + } else if (!option.evil && left && left.value === 'document' && + (m === 'write' || m === 'writeln')) { + warning("document.write can be a form of eval.", left); + } else if (option.adsafe) { + if (left && left.value === 'ADSAFE') { + if (m === 'id' || m === 'lib') { + warning("ADsafe violation.", that); + } else if (m === 'go') { + if (xmode !== 'script') { + warning("ADsafe violation.", that); + } else if (adsafe_went || nexttoken.id !== '(' || + peek(0).id !== '(string)' || + peek(0).value !== adsafe_id || + peek(1).id !== ',') { + error("ADsafe violation: go.", that); + } + adsafe_went = true; + adsafe_may = false; + } + } + } + if (!option.evil && (m === 'eval' || m === 'execScript')) { + warning('eval is evil.'); + } else if (option.safe) { + for (;;) { + if (banned[m] === true) { + warning("ADsafe restricted word '{a}'.", token, m); + } + if (typeof predefined[left.value] !== 'boolean' || + nexttoken.id === '(') { + break; + } + if (standard_member[m] === true) { + if (nexttoken.id === '.') { + warning("ADsafe violation.", that); + } + break; + } + if (nexttoken.id !== '.') { + warning("ADsafe violation.", that); + break; + } + advance('.'); + token.left = that; + token.right = m; + that = token; + m = identifier(); + if (typeof m === 'string') { + countMember(m); + } + } + } + return that; + }, 160, true); + + infix('(', function (left, that) { + adjacent(prevtoken, token); + nospace(); + if (option.immed && !left.immed && left.id === 'function') { + warning("Wrap an immediate function invocation in parentheses " + + "to assist the reader in understanding that the expression " + + "is the result of a function, and not the function itself."); + } + var n = 0, + p = []; + if (left) { + if (left.type === '(identifier)') { + if (left.value.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { + if (left.value !== 'Number' && left.value !== 'String' && + left.value !== 'Boolean' && + left.value !== 'Date') { + if (left.value === 'Math') { + warning("Math is not a function.", left); + } else if (option.newcap) { + warning( +"Missing 'new' prefix when invoking a constructor.", left); + } + } + } + } else if (left.id === '.') { + if (option.safe && left.left.value === 'Math' && + left.right === 'random') { + warning("ADsafe violation.", left); + } + } + } + if (nexttoken.id !== ')') { + for (;;) { + p[p.length] = parse(10); + n += 1; + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + advance(')'); + nospace(prevtoken, token); + if (typeof left === 'object') { + if (left.value === 'parseInt' && n === 1) { + warning("Missing radix parameter.", left); + } + if (!option.evil) { + if (left.value === 'eval' || left.value === 'Function' || + left.value === 'execScript') { + warning("eval is evil.", left); + } else if (p[0] && p[0].id === '(string)' && + (left.value === 'setTimeout' || + left.value === 'setInterval')) { + warning( + "Implied eval is evil. Pass a function instead of a string.", left); + } + } + if (!left.identifier && left.id !== '.' && left.id !== '[' && + left.id !== '(' && left.id !== '&&' && left.id !== '||' && + left.id !== '?') { + warning("Bad invocation.", left); + } + } + that.left = left; + return that; + }, 155, true).exps = true; + + prefix('(', function () { + nospace(); + if (nexttoken.id === 'function') { + nexttoken.immed = true; + } + var v = parse(0); + advance(')', this); + nospace(prevtoken, token); + if (option.immed && v.id === 'function') { + if (nexttoken.id === '(') { + warning( +"Move the invocation into the parens that contain the function.", nexttoken); + } else { + warning( +"Do not wrap function literals in parens unless they are to be immediately invoked.", + this); + } + } + return v; + }); + + infix('[', function (left, that) { + nospace(); + var e = parse(0), s; + if (e && e.type === '(string)') { + if (option.safe && banned[e.value] === true) { + warning("ADsafe restricted word '{a}'.", that, e.value); + } else if (!option.evil && + (e.value === 'eval' || e.value === 'execScript')) { + warning("eval is evil.", that); + } else if (option.safe && + (e.value.charAt(0) === '_' || e.value.charAt(0) === '-')) { + warning("ADsafe restricted subscript '{a}'.", that, e.value); + } + countMember(e.value); + if (!option.sub && ix.test(e.value)) { + s = syntax[e.value]; + if (!s || !s.reserved) { + warning("['{a}'] is better written in dot notation.", + e, e.value); + } + } + } else if (!e || e.type !== '(number)' || e.value < 0) { + if (option.safe) { + warning('ADsafe subscripting.'); + } + } + advance(']', that); + nospace(prevtoken, token); + that.left = left; + that.right = e; + return that; + }, 160, true); + + prefix('[', function () { + var b = token.line !== nexttoken.line; + this.first = []; + if (b) { + indent += option.indent; + if (nexttoken.from === indent + option.indent) { + indent += option.indent; + } + } + while (nexttoken.id !== '(end)') { + while (nexttoken.id === ',') { + warning("Extra comma."); + advance(','); + } + if (nexttoken.id === ']') { + break; + } + if (b && token.line !== nexttoken.line) { + indentation(); + } + this.first.push(parse(10)); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.id === ']' && !option.es5) { + warning("Extra comma.", token); + break; + } + } else { + break; + } + } + if (b) { + indent -= option.indent; + indentation(); + } + advance(']', this); + return this; + }, 160); + + + function property_name() { + var id = optionalidentifier(true); + if (!id) { + if (nexttoken.id === '(string)') { + id = nexttoken.value; + if (option.adsafe && + (id.charAt(0) === '_' || + id.charAt(id.length - 1) === '_')) { + warning("Unexpected {a} in '{b}'.", token, + "dangling '_'", id); + } + advance(); + } else if (nexttoken.id === '(number)') { + id = nexttoken.value.toString(); + advance(); + } + } + return id; + } + + + function functionparams() { + var i, t = nexttoken, p = []; + advance('('); + nospace(); + if (nexttoken.id === ')') { + advance(')'); + nospace(prevtoken, token); + return; + } + for (;;) { + i = identifier(); + p.push(i); + addlabel(i, 'parameter'); + if (nexttoken.id === ',') { + comma(); + } else { + advance(')', t); + nospace(prevtoken, token); + return p; + } + } + } + + + function doFunction(i, statement) { + var f, s = scope; + scope = Object.create(s); + funct = { + '(name)' : i || '"' + anonname + '"', + '(line)' : nexttoken.line, + '(context)' : funct, + '(breakage)' : 0, + '(loopage)' : 0, + '(scope)' : scope, + '(statement)': statement + }; + f = funct; + token.funct = funct; + functions.push(funct); + if (i) { + addlabel(i, 'function'); + } + funct['(params)'] = functionparams(); + + block(false); + scope = s; + funct['(last)'] = token.line; + funct = funct['(context)']; + return f; + } + + + (function (x) { + x.nud = function () { + var b, f, i, j, p, seen = {}, t; + b = token.line !== nexttoken.line; + if (b) { + indent += option.indent; + if (nexttoken.from === indent + option.indent) { + indent += option.indent; + } + } + for (;;) { + if (nexttoken.id === '}') { + break; + } + if (b) { + indentation(); + } + if (nexttoken.value === 'get' && peek().id !== ':') { + advance('get'); + if (!option.es5) { + error("get/set are ES5 features."); + } + i = property_name(); + if (!i) { + error("Missing property name."); + } + t = nexttoken; + adjacent(token, nexttoken); + f = doFunction(i); + if (funct['(loopage)']) { + warning("Don't make functions within a loop.", t); + } + p = f['(params)']; + if (p) { + warning("Unexpected parameter '{a}' in get {b} function.", t, p[0], i); + } + adjacent(token, nexttoken); + advance(','); + indentation(); + advance('set'); + j = property_name(); + if (i !== j) { + error("Expected {a} and instead saw {b}.", token, i, j); + } + t = nexttoken; + adjacent(token, nexttoken); + f = doFunction(i); + p = f['(params)']; + if (!p || p.length !== 1 || p[0] !== 'value') { + warning("Expected (value) in set {a} function.", t, i); + } + } else { + i = property_name(); + if (typeof i !== 'string') { + break; + } + advance(':'); + nonadjacent(token, nexttoken); + parse(10); + } + if (seen[i] === true) { + warning("Duplicate member '{a}'.", nexttoken, i); + } + seen[i] = true; + countMember(i); + if (nexttoken.id === ',') { + comma(); + if (nexttoken.id === ',') { + warning("Extra comma.", token); + } else if (nexttoken.id === '}' && !option.es5) { + warning("Extra comma.", token); + } + } else { + break; + } + } + if (b) { + indent -= option.indent; + indentation(); + } + advance('}', this); + return this; + }; + x.fud = function () { + error("Expected to see a statement and instead saw a block.", token); + }; + }(delim('{'))); + + + var varstatement = function varstatement(prefix) { + +// JavaScript does not have block scope. It only has function scope. So, +// declaring a variable in a block can have unexpected consequences. + + var id, name, value; + + if (funct['(onevar)'] && option.onevar) { + warning("Too many var statements."); + } else if (!funct['(global)']) { + funct['(onevar)'] = true; + } + this.first = []; + for (;;) { + nonadjacent(token, nexttoken); + id = identifier(); + if (funct['(global)'] && predefined[id] === false) { + warning("Redefinition of '{a}'.", token, id); + } + addlabel(id, 'unused'); + if (prefix) { + break; + } + name = token; + this.first.push(token); + if (nexttoken.id === '=') { + nonadjacent(token, nexttoken); + advance('='); + nonadjacent(token, nexttoken); + if (nexttoken.id === 'undefined') { + warning("It is not necessary to initialize '{a}' to 'undefined'.", token, id); + } + if (peek(0).id === '=' && nexttoken.identifier) { + error("Variable {a} was not declared correctly.", + nexttoken, nexttoken.value); + } + value = parse(0); + name.first = value; + } + if (nexttoken.id !== ',') { + break; + } + comma(); + } + return this; + }; + + + stmt('var', varstatement).exps = true; + + + blockstmt('function', function () { + if (inblock) { + warning( +"Function statements cannot be placed in blocks. Use a function expression or move the statement to the top of the outer function.", token); + + } + var i = identifier(); + adjacent(token, nexttoken); + addlabel(i, 'unction'); + doFunction(i, true); + if (nexttoken.id === '(' && nexttoken.line === token.line) { + error( +"Function statements are not invocable. Wrap the whole function invocation in parens."); + } + return this; + }); + + prefix('function', function () { + var i = optionalidentifier(); + if (i) { + adjacent(token, nexttoken); + } else { + nonadjacent(token, nexttoken); + } + doFunction(i); + if (funct['(loopage)']) { + warning("Don't make functions within a loop."); + } + return this; + }); + + blockstmt('if', function () { + var t = nexttoken; + advance('('); + nonadjacent(this, t); + nospace(); + parse(20); + if (nexttoken.id === '=') { + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + parse(20); + } + advance(')', t); + nospace(prevtoken, token); + block(true); + if (nexttoken.id === 'else') { + nonadjacent(token, nexttoken); + advance('else'); + if (nexttoken.id === 'if' || nexttoken.id === 'switch') { + statement(true); + } else { + block(true); + } + } + return this; + }); + + blockstmt('try', function () { + var b, e, s; + if (option.adsafe) { + warning("ADsafe try violation.", this); + } + block(false); + if (nexttoken.id === 'catch') { + advance('catch'); + nonadjacent(token, nexttoken); + advance('('); + s = scope; + scope = Object.create(s); + e = nexttoken.value; + if (nexttoken.type !== '(identifier)') { + warning("Expected an identifier and instead saw '{a}'.", + nexttoken, e); + } else { + addlabel(e, 'exception'); + } + advance(); + advance(')'); + block(false); + b = true; + scope = s; + } + if (nexttoken.id === 'finally') { + advance('finally'); + block(false); + return; + } else if (!b) { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, 'catch', nexttoken.value); + } + return this; + }); + + blockstmt('while', function () { + var t = nexttoken; + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + advance('('); + nonadjacent(this, t); + nospace(); + parse(20); + if (nexttoken.id === '=') { + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + parse(20); + } + advance(')', t); + nospace(prevtoken, token); + block(true); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + }).labelled = true; + + reserve('with'); + + blockstmt('switch', function () { + var t = nexttoken, + g = false; + funct['(breakage)'] += 1; + advance('('); + nonadjacent(this, t); + nospace(); + this.condition = parse(20); + advance(')', t); + nospace(prevtoken, token); + nonadjacent(token, nexttoken); + t = nexttoken; + advance('{'); + nonadjacent(token, nexttoken); + indent += option.indent; + this.cases = []; + for (;;) { + switch (nexttoken.id) { + case 'case': + switch (funct['(verb)']) { + case 'break': + case 'case': + case 'continue': + case 'return': + case 'switch': + case 'throw': + break; + default: + warning( + "Expected a 'break' statement before 'case'.", + token); + } + indentation(-option.indent); + advance('case'); + this.cases.push(parse(20)); + g = true; + advance(':'); + funct['(verb)'] = 'case'; + break; + case 'default': + switch (funct['(verb)']) { + case 'break': + case 'continue': + case 'return': + case 'throw': + break; + default: + warning( + "Expected a 'break' statement before 'default'.", + token); + } + indentation(-option.indent); + advance('default'); + g = true; + advance(':'); + break; + case '}': + indent -= option.indent; + indentation(); + advance('}', t); + if (this.cases.length === 1 || this.condition.id === 'true' || + this.condition.id === 'false') { + warning("This 'switch' should be an 'if'.", this); + } + funct['(breakage)'] -= 1; + funct['(verb)'] = undefined; + return; + case '(end)': + error("Missing '{a}'.", nexttoken, '}'); + return; + default: + if (g) { + switch (token.id) { + case ',': + error("Each value should have its own case label."); + return; + case ':': + statements(); + break; + default: + error("Missing ':' on a case clause.", token); + } + } else { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, 'case', nexttoken.value); + } + } + } + }).labelled = true; + + stmt('debugger', function () { + if (!option.debug) { + warning("All 'debugger' statements should be removed."); + } + return this; + }).exps = true; + + (function () { + var x = stmt('do', function () { + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + this.first = block(true); + advance('while'); + var t = nexttoken; + nonadjacent(token, t); + advance('('); + nospace(); + parse(20); + if (nexttoken.id === '=') { + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + parse(20); + } + advance(')', t); + nospace(prevtoken, token); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + }); + x.labelled = true; + x.exps = true; + }()); + + blockstmt('for', function () { + var f = option.forin, s, t = nexttoken; + funct['(breakage)'] += 1; + funct['(loopage)'] += 1; + advance('('); + nonadjacent(this, t); + nospace(); + if (peek(nexttoken.id === 'var' ? 1 : 0).id === 'in') { + if (nexttoken.id === 'var') { + advance('var'); + varstatement(true); + } else { + switch (funct[nexttoken.value]) { + case 'unused': + funct[nexttoken.value] = 'var'; + break; + case 'var': + break; + default: + warning("Bad for in variable '{a}'.", + nexttoken, nexttoken.value); + } + advance(); + } + advance('in'); + parse(20); + advance(')', t); + s = block(true); + if (!f && (s.length > 1 || typeof s[0] !== 'object' || + s[0].value !== 'if')) { + warning("The body of a for in should be wrapped in an if statement to filter unwanted properties from the prototype.", this); + } + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + } else { + if (nexttoken.id !== ';') { + if (nexttoken.id === 'var') { + advance('var'); + varstatement(); + } else { + for (;;) { + parse(0, 'for'); + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + } + nolinebreak(token); + advance(';'); + if (nexttoken.id !== ';') { + parse(20); + if (nexttoken.id === '=') { + warning("Expected a conditional expression and instead saw an assignment."); + advance('='); + parse(20); + } + } + nolinebreak(token); + advance(';'); + if (nexttoken.id === ';') { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, ')', ';'); + } + if (nexttoken.id !== ')') { + for (;;) { + parse(0, 'for'); + if (nexttoken.id !== ',') { + break; + } + comma(); + } + } + advance(')', t); + nospace(prevtoken, token); + block(true); + funct['(breakage)'] -= 1; + funct['(loopage)'] -= 1; + return this; + } + }).labelled = true; + + + stmt('break', function () { + var v = nexttoken.value; + if (funct['(breakage)'] === 0) { + warning("Unexpected '{a}'.", nexttoken, this.value); + } + nolinebreak(this); + if (nexttoken.id !== ';') { + if (token.line === nexttoken.line) { + if (funct[v] !== 'label') { + warning("'{a}' is not a statement label.", nexttoken, v); + } else if (scope[v] !== funct) { + warning("'{a}' is out of scope.", nexttoken, v); + } + this.first = nexttoken; + advance(); + } + } + reachable('break'); + return this; + }).exps = true; + + + stmt('continue', function () { + var v = nexttoken.value; + if (funct['(breakage)'] === 0) { + warning("Unexpected '{a}'.", nexttoken, this.value); + } + nolinebreak(this); + if (nexttoken.id !== ';') { + if (token.line === nexttoken.line) { + if (funct[v] !== 'label') { + warning("'{a}' is not a statement label.", nexttoken, v); + } else if (scope[v] !== funct) { + warning("'{a}' is out of scope.", nexttoken, v); + } + this.first = nexttoken; + advance(); + } + } else if (!funct['(loopage)']) { + warning("Unexpected '{a}'.", nexttoken, this.value); + } + reachable('continue'); + return this; + }).exps = true; + + + stmt('return', function () { + nolinebreak(this); + if (nexttoken.id === '(regexp)') { + warning("Wrap the /regexp/ literal in parens to disambiguate the slash operator."); + } + if (nexttoken.id !== ';' && !nexttoken.reach) { + nonadjacent(token, nexttoken); + this.first = parse(20); + } + reachable('return'); + return this; + }).exps = true; + + + stmt('throw', function () { + nolinebreak(this); + nonadjacent(token, nexttoken); + this.first = parse(20); + reachable('throw'); + return this; + }).exps = true; + + reserve('void'); + +// Superfluous reserved words + + reserve('class'); + reserve('const'); + reserve('enum'); + reserve('export'); + reserve('extends'); + reserve('import'); + reserve('super'); + + reserve('let'); + reserve('yield'); + reserve('implements'); + reserve('interface'); + reserve('package'); + reserve('private'); + reserve('protected'); + reserve('public'); + reserve('static'); + + +// Parse JSON + + function jsonValue() { + + function jsonObject() { + var o = {}, t = nexttoken; + advance('{'); + if (nexttoken.id !== '}') { + for (;;) { + if (nexttoken.id === '(end)') { + error("Missing '}' to match '{' from line {a}.", + nexttoken, t.line); + } else if (nexttoken.id === '}') { + warning("Unexpected comma.", token); + break; + } else if (nexttoken.id === ',') { + error("Unexpected comma.", nexttoken); + } else if (nexttoken.id !== '(string)') { + warning("Expected a string and instead saw {a}.", + nexttoken, nexttoken.value); + } + if (o[nexttoken.value] === true) { + warning("Duplicate key '{a}'.", + nexttoken, nexttoken.value); + } else if (nexttoken.value === '__proto__') { + warning("Stupid key '{a}'.", + nexttoken, nexttoken.value); + } else { + o[nexttoken.value] = true; + } + advance(); + advance(':'); + jsonValue(); + if (nexttoken.id !== ',') { + break; + } + advance(','); + } + } + advance('}'); + } + + function jsonArray() { + var t = nexttoken; + advance('['); + if (nexttoken.id !== ']') { + for (;;) { + if (nexttoken.id === '(end)') { + error("Missing ']' to match '[' from line {a}.", + nexttoken, t.line); + } else if (nexttoken.id === ']') { + warning("Unexpected comma.", token); + break; + } else if (nexttoken.id === ',') { + error("Unexpected comma.", nexttoken); + } + jsonValue(); + if (nexttoken.id !== ',') { + break; + } + advance(','); + } + } + advance(']'); + } + + switch (nexttoken.id) { + case '{': + jsonObject(); + break; + case '[': + jsonArray(); + break; + case 'true': + case 'false': + case 'null': + case '(number)': + case '(string)': + advance(); + break; + case '-': + advance('-'); + if (token.character !== nexttoken.from) { + warning("Unexpected space after '-'.", token); + } + adjacent(token, nexttoken); + advance('(number)'); + break; + default: + error("Expected a JSON value.", nexttoken); + } + } + + +// The actual JSLINT function itself. + + var itself = function (s, o) { + var a, i; + JSLINT.errors = []; + predefined = Object.create(standard); + if (o) { + a = o.predef; + if (a instanceof Array) { + for (i = 0; i < a.length; i += 1) { + predefined[a[i]] = true; + } + } + if (o.adsafe) { + o.safe = true; + } + if (o.safe) { + o.browser = + o.css = + o.debug = + o.devel = + o.evil = + o.forin = + o.on = + o.rhino = + o.windows = + o.sub = + o.widget = false; + + o.eqeqeq = + o.nomen = + o.safe = + o.strict = + o.undef = true; + + predefined.Date = + predefined['eval'] = + predefined.Function = + predefined.Object = null; + + predefined.ADSAFE = + predefined.lib = false; + } + option = o; + } else { + option = {}; + } + option.indent = option.indent || 4; + option.maxerr = option.maxerr || 50; + adsafe_id = ''; + adsafe_may = false; + adsafe_went = false; + approved = {}; + if (option.approved) { + for (i = 0; i < option.approved.length; i += 1) { + approved[option.approved[i]] = option.approved[i]; + } + } else { + approved.test = 'test'; + } + tab = ''; + for (i = 0; i < option.indent; i += 1) { + tab += ' '; + } + indent = 1; + global = Object.create(predefined); + scope = global; + funct = { + '(global)': true, + '(name)': '(global)', + '(scope)': scope, + '(breakage)': 0, + '(loopage)': 0 + }; + functions = [funct]; + ids = {}; + urls = []; + src = false; + xmode = false; + stack = null; + member = {}; + membersOnly = null; + implied = {}; + inblock = false; + lookahead = []; + jsonmode = false; + warnings = 0; + lex.init(s); + prereg = true; + strict_mode = false; + + prevtoken = token = nexttoken = syntax['(begin)']; + assume(); + + try { + advance(); + if (nexttoken.value.charAt(0) === '<') { + html(); + if (option.adsafe && !adsafe_went) { + warning("ADsafe violation: Missing ADSAFE.go.", this); + } + } else { + switch (nexttoken.id) { + case '{': + case '[': + option.laxbreak = true; + jsonmode = true; + jsonValue(); + break; + case '@': + case '*': + case '#': + case '.': + case ':': + xmode = 'style'; + advance(); + if (token.id !== '@' || !nexttoken.identifier || + nexttoken.value !== 'charset' || token.line !== 1 || + token.from !== 1) { + error('A css file should begin with @charset "UTF-8";'); + } + advance(); + if (nexttoken.type !== '(string)' && + nexttoken.value !== 'UTF-8') { + error('A css file should begin with @charset "UTF-8";'); + } + advance(); + advance(';'); + styles(); + break; + + default: + if (option.adsafe && option.fragment) { + error("Expected '{a}' and instead saw '{b}'.", + nexttoken, '
', nexttoken.value); + } + statements('lib'); + } + } + advance('(end)'); + } catch (e) { + if (e) { + JSLINT.errors.push({ + reason : e.message, + line : e.line || nexttoken.line, + character : e.character || nexttoken.from + }, null); + } + } + return JSLINT.errors.length === 0; + }; + + function is_array(o) { + return Object.prototype.toString.apply(o) === '[object Array]'; + } + + function to_array(o) { + var a = [], k; + for (k in o) { + if (is_own(o, k)) { + a.push(k); + } + } + return a; + } + + +// Data summary. + + itself.data = function () { + + var data = {functions: []}, fu, globals, implieds = [], f, i, j, + members = [], n, unused = [], v; + if (itself.errors.length) { + data.errors = itself.errors; + } + + if (jsonmode) { + data.json = true; + } + + for (n in implied) { + if (is_own(implied, n)) { + implieds.push({ + name: n, + line: implied[n] + }); + } + } + if (implieds.length > 0) { + data.implieds = implieds; + } + + if (urls.length > 0) { + data.urls = urls; + } + + globals = to_array(scope); + if (globals.length > 0) { + data.globals = globals; + } + + for (i = 1; i < functions.length; i += 1) { + f = functions[i]; + fu = {}; + for (j = 0; j < functionicity.length; j += 1) { + fu[functionicity[j]] = []; + } + for (n in f) { + if (is_own(f, n) && n.charAt(0) !== '(') { + v = f[n]; + if (v === 'unction') { + v = 'unused'; + } + if (is_array(fu[v])) { + fu[v].push(n); + if (v === 'unused') { + unused.push({ + name: n, + line: f['(line)'], + 'function': f['(name)'] + }); + } + } + } + } + for (j = 0; j < functionicity.length; j += 1) { + if (fu[functionicity[j]].length === 0) { + delete fu[functionicity[j]]; + } + } + fu.name = f['(name)']; + fu.param = f['(params)']; + fu.line = f['(line)']; + fu.last = f['(last)']; + data.functions.push(fu); + } + + if (unused.length > 0) { + data.unused = unused; + } + + members = []; + for (n in member) { + if (typeof member[n] === 'number') { + data.member = member; + break; + } + } + + return data; + }; + + itself.report = function (option) { + var data = itself.data(); + + var a = [], c, e, err, f, i, k, l, m = '', n, o = [], s; + + function detail(h, array) { + var b, i, singularity; + if (array) { + o.push('
' + h + ' '); + array = array.sort(); + for (i = 0; i < array.length; i += 1) { + if (array[i] !== singularity) { + singularity = array[i]; + o.push((b ? ', ' : '') + singularity); + b = true; + } + } + o.push('
'); + } + } + + + if (data.errors || data.implieds || data.unused) { + err = true; + o.push('
Error:'); + if (data.errors) { + for (i = 0; i < data.errors.length; i += 1) { + c = data.errors[i]; + if (c) { + e = c.evidence || ''; + o.push('

Problem' + (isFinite(c.line) ? ' at line ' + + c.line + ' character ' + c.character : '') + + ': ' + c.reason.entityify() + + '

' + + (e && (e.length > 80 ? e.slice(0, 77) + '...' : + e).entityify()) + '

'); + } + } + } + + if (data.implieds) { + s = []; + for (i = 0; i < data.implieds.length; i += 1) { + s[i] = '' + data.implieds[i].name + ' ' + + data.implieds[i].line + ''; + } + o.push('

Implied global: ' + s.join(', ') + '

'); + } + + if (data.unused) { + s = []; + for (i = 0; i < data.unused.length; i += 1) { + s[i] = '' + data.unused[i].name + ' ' + + data.unused[i].line + ' ' + + data.unused[i]['function'] + ''; + } + o.push('

Unused variable: ' + s.join(', ') + '

'); + } + if (data.json) { + o.push('

JSON: bad.

'); + } + o.push('
'); + } + + if (!option) { + + o.push('
'); + + if (data.urls) { + detail("URLs
", data.urls, '
'); + } + + if (xmode === 'style') { + o.push('

CSS.

'); + } else if (data.json && !err) { + o.push('

JSON: good.

'); + } else if (data.globals) { + o.push('
Global ' + + data.globals.sort().join(', ') + '
'); + } else { + o.push('
No new global variables introduced.
'); + } + + for (i = 0; i < data.functions.length; i += 1) { + f = data.functions[i]; + + o.push('
' + f.line + '-' + + f.last + ' ' + (f.name || '') + '(' + + (f.param ? f.param.join(', ') : '') + ')
'); + detail('Unused', f.unused); + detail('Closure', f.closure); + detail('Variable', f['var']); + detail('Exception', f.exception); + detail('Outer', f.outer); + detail('Global', f.global); + detail('Label', f.label); + } + + if (data.member) { + a = to_array(data.member); + if (a.length) { + a = a.sort(); + m = '
/*members ';
+                    l = 10;
+                    for (i = 0; i < a.length; i += 1) {
+                        k = a[i];
+                        n = k.name();
+                        if (l + n.length > 72) {
+                            o.push(m + '
'); + m = ' '; + l = 1; + } + l += n.length + 2; + if (data.member[k] === 1) { + n = '' + n + ''; + } + if (i < a.length - 1) { + n += ', '; + } + m += n; + } + o.push(m + '
*/
'); + } + o.push('
'); + } + } + return o.join(''); + }; + itself.jslint = itself; + + itself.edition = '2010-09-16'; + + return itself; + +}()); + +exports.JSLINT = JSLINT; diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/.gitignore b/emacs.d/lisp/jshint-mode/node_modules/formidable/.gitignore new file mode 100644 index 0000000..b72f74f --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/.gitignore @@ -0,0 +1,4 @@ +/test/tmp +*.upload +*.un~ +/node_modules diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/Makefile b/emacs.d/lisp/jshint-mode/node_modules/formidable/Makefile new file mode 100644 index 0000000..8db19bc --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/Makefile @@ -0,0 +1,9 @@ +SHELL := /bin/bash + +test: + @find test/{simple,integration,system}/test-*.js | xargs -n 1 -t node + +clean: + rm test/tmp/* + +.PHONY: test clean diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/Readme.md b/emacs.d/lisp/jshint-mode/node_modules/formidable/Readme.md new file mode 100644 index 0000000..e15c583 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/Readme.md @@ -0,0 +1,258 @@ +# Formidable + +## Purpose + +A node.js module for parsing form data, especially file uploads. + +## Current status + +This module was developed for [Transloadit](http://transloadit.com/), a service focused on uploading +and encoding images and videos. It has been battle-tested against hundreds of GB of file uploads from +a big variety of clients and is considered production-ready. + +## Features + +* Fast (~500mb/sec), non-buffering multipart parser +* Automatically writing file uploads to disk +* Low memory footprint +* Graceful error handling +* Very high test coverage + +## Changelog + +### v1.0.2 + +* Exclude node\_modules folder from git +* Implement new `'aborted'` event +* Fix files in example folder to work with recent node versions +* Make gently a devDependency + +[See Commits](https://github.com/felixge/node-formidable/compare/v1.0.1...v1.0.2) + +### v1.0.1 + +* Fix package.json to refer to proper main directory. (#68, Dean Landolt) + +[See Commits](https://github.com/felixge/node-formidable/compare/v1.0.0...v1.0.1) + +### v1.0.0 + +* Add support for multipart boundaries that are quoted strings. (Jeff Craig) + +This marks the begin of the development on version 2.0 which will include +several architecural improvements. + +[See Commits](https://github.com/felixge/node-formidable/compare/v0.9.11...v1.0.0) + +### v0.9.11 + +* Emit `'progress'` event when receiving data, regardless of parsing it. (Tim Koschützki) +* Use [W3C FileAPI Draft](http://dev.w3.org/2006/webapi/FileAPI/) properties for File class + +**Important:** The old property names of the File class will be removed in a +future release. + +[See Commits](https://github.com/felixge/node-formidable/compare/v0.9.10...v0.9.11) + +### Older releases + +These releases were done before starting to maintain the above Changelog: + +* [v0.9.10](https://github.com/felixge/node-formidable/compare/v0.9.9...v0.9.10) +* [v0.9.9](https://github.com/felixge/node-formidable/compare/v0.9.8...v0.9.9) +* [v0.9.8](https://github.com/felixge/node-formidable/compare/v0.9.7...v0.9.8) +* [v0.9.7](https://github.com/felixge/node-formidable/compare/v0.9.6...v0.9.7) +* [v0.9.6](https://github.com/felixge/node-formidable/compare/v0.9.5...v0.9.6) +* [v0.9.5](https://github.com/felixge/node-formidable/compare/v0.9.4...v0.9.5) +* [v0.9.4](https://github.com/felixge/node-formidable/compare/v0.9.3...v0.9.4) +* [v0.9.3](https://github.com/felixge/node-formidable/compare/v0.9.2...v0.9.3) +* [v0.9.2](https://github.com/felixge/node-formidable/compare/v0.9.1...v0.9.2) +* [v0.9.1](https://github.com/felixge/node-formidable/compare/v0.9.0...v0.9.1) +* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0) +* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0) +* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0) +* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0) +* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0) +* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0) +* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0) +* [v0.9.0](https://github.com/felixge/node-formidable/compare/v0.8.0...v0.9.0) +* [v0.1.0](https://github.com/felixge/node-formidable/commits/v0.1.0) + +## Installation + +Via [npm](http://github.com/isaacs/npm): + + npm install formidable@latest + +Manually: + + git clone git://github.com/felixge/node-formidable.git formidable + vim my.js + # var formidable = require('./formidable'); + +Note: Formidable requires [gently](http://github.com/felixge/node-gently) to run the unit tests, but you won't need it for just using the library. + +## Example + +Parse an incoming file upload. + + var formidable = require('formidable'), + http = require('http'), + + sys = require('sys'); + + http.createServer(function(req, res) { + if (req.url == '/upload' && req.method.toLowerCase() == 'post') { + // parse a file upload + var form = new formidable.IncomingForm(); + form.parse(req, function(err, fields, files) { + res.writeHead(200, {'content-type': 'text/plain'}); + res.write('received upload:\n\n'); + res.end(sys.inspect({fields: fields, files: files})); + }); + return; + } + + // show a file upload form + res.writeHead(200, {'content-type': 'text/html'}); + res.end( + '
'+ + '
'+ + '
'+ + ''+ + '
' + ); + }); + +## API + +### formdiable.IncomingForm + +#### new formdiable.IncomingForm() + +Creates a new incoming form. + +#### incomingForm.encoding = 'utf-8' + +The encoding to use for incoming form fields. + +#### incomingForm.uploadDir = '/tmp' + +The directory for placing file uploads in. You can later on move them using `fs.rename()`. + +#### incomingForm.keepExtensions = false + +If you want the files written to `incomingForm.uploadDir` to include the extensions of the original files, set this property to `true`. + +#### incomingForm.type + +Either 'multipart' or 'urlencoded' depending on the incoming request. + +#### incomingForm.maxFieldsSize = 2 * 1024 * 1024 + +Limits the amount of memory a field (not file) can allocate in bytes. +I this value is exceeded, an `'error'` event is emitted. The default +size is 2MB. + +#### incomingForm.bytesReceived + +The amount of bytes received for this form so far. + +#### incomingForm.bytesExpected + +The expected number of bytes in this form. + +#### incomingForm.parse(request, [cb]) + +Parses an incoming node.js `request` containing form data. If `cb` is provided, all fields an files are collected and passed to the callback: + + incomingForm.parse(req, function(err, fields, files) { + // ... + }); + +#### incomingForm.onPart(part) + +You may overwrite this method if you are interested in directly accessing the multipart stream. Doing so will disable any `'field'` / `'file'` events processing which would occur otherwise, making you fully responsible for handling the processing. + + incomingForm.onPart = function(part) { + part.addListener('data', function() { + // ... + }); + } + +If you want to use formidable to only handle certain parts for you, you can do so: + + incomingForm.onPart = function(part) { + if (!part.filename) { + // let formidable handle all non-file parts + incomingForm.handlePart(part); + } + } + +Check the code in this method for further inspiration. + +#### Event: 'progress' (bytesReceived, bytesExpected) + +Emitted after each incoming chunk of data that has been parsed. Can be used to roll your own progress bar. + +#### Event: 'field' (name, value) + +Emitted whenever a field / value pair has been received. + +#### Event: 'fileBegin' (name, file) + +Emitted whenever a new file is detected in the upload stream. Use this even if +you want to stream the file to somewhere else while buffering the upload on +the file system. + +#### Event: 'file' (name, file) + +Emitted whenever a field / file pair has been received. `file` is an instance of `File`. + +#### Event: 'error' (err) + +Emitted when there is an error processing the incoming form. A request that experiences an error is automatically paused, you will have to manually call `request.resume()` if you want the request to continue firing `'data'` events. + +#### Event: 'aborted' + +Emitted when the request was aborted by the user. Right now this can be due to a 'timeout' or 'close' event on the socket. In the future there will be a seperate 'timeout' event (needs a change in the node core). + +#### Event: 'end' () + +Emitted when the entire request has been received, and all contained files have finished flushing to disk. This is a great place for you to send your response. + +### formdiable.File + +#### file.size = 0 + +The size of the uploade file in bytes. If the file is still being uploaded (see `'fileBegin'` event), this property says how many bytes of the file have been written to disk yet. + +#### file.path = null + +The path this file is being written to. You can modify this in the `'fileBegin'` event in +case you are unhappy with the way formidable generates a temporary path for your files. + +#### file.name = null + +The name this file had according to the uploading client. + +#### file.type = null + +The mime type of this file, according to the uploading client. + +#### file.lastModifiedDate = null + +A date object (or `null`) containing the time this file was last written to. Mostly +here for compatiblity with the [W3C File API Draft](http://dev.w3.org/2006/webapi/FileAPI/). + +## License + +Formidable is licensed under the MIT license. + +## Ports + +* [multipart-parser](http://github.com/FooBarWidget/multipart-parser): a C++ parser based on formidable + +## Credits + +* [Ryan Dahl](http://twitter.com/ryah) for his work on [http-parser](http://github.com/ry/http-parser) which heavily inspired multipart_parser.js diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/TODO b/emacs.d/lisp/jshint-mode/node_modules/formidable/TODO new file mode 100644 index 0000000..e1107f2 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/TODO @@ -0,0 +1,3 @@ +- Better bufferMaxSize handling approach +- Add tests for JSON parser pull request and merge it +- Implement QuerystringParser the same way as MultipartParser diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/benchmark/bench-multipart-parser.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/benchmark/bench-multipart-parser.js new file mode 100644 index 0000000..bff41f1 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/benchmark/bench-multipart-parser.js @@ -0,0 +1,70 @@ +require('../test/common'); +var multipartParser = require('../lib/multipart_parser'), + MultipartParser = multipartParser.MultipartParser, + parser = new MultipartParser(), + Buffer = require('buffer').Buffer, + boundary = '-----------------------------168072824752491622650073', + mb = 100, + buffer = createMultipartBuffer(boundary, mb * 1024 * 1024), + callbacks = + { partBegin: -1, + partEnd: -1, + headerField: -1, + headerValue: -1, + partData: -1, + end: -1, + }; + + +parser.initWithBoundary(boundary); +parser.onHeaderField = function() { + callbacks.headerField++; +}; + +parser.onHeaderValue = function() { + callbacks.headerValue++; +}; + +parser.onPartBegin = function() { + callbacks.partBegin++; +}; + +parser.onPartData = function() { + callbacks.partData++; +}; + +parser.onPartEnd = function() { + callbacks.partEnd++; +}; + +parser.onEnd = function() { + callbacks.end++; +}; + +var start = +new Date(), + nparsed = parser.write(buffer), + duration = +new Date - start, + mbPerSec = (mb / (duration / 1000)).toFixed(2); + +console.log(mbPerSec+' mb/sec'); + +assert.equal(nparsed, buffer.length); + +function createMultipartBuffer(boundary, size) { + var head = + '--'+boundary+'\r\n' + + 'content-disposition: form-data; name="field1"\r\n' + + '\r\n' + , tail = '\r\n--'+boundary+'--\r\n' + , buffer = new Buffer(size); + + buffer.write(head, 'ascii', 0); + buffer.write(tail, 'ascii', buffer.length - tail.length); + return buffer; +} + +process.on('exit', function() { + for (var k in callbacks) { + assert.equal(0, callbacks[k], k+' count off by '+callbacks[k]); + } +}); diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/example/post.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/example/post.js new file mode 100644 index 0000000..f6c15a6 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/example/post.js @@ -0,0 +1,43 @@ +require('../test/common'); +var http = require('http'), + util = require('util'), + formidable = require('formidable'), + server; + +server = http.createServer(function(req, res) { + if (req.url == '/') { + res.writeHead(200, {'content-type': 'text/html'}); + res.end( + '
'+ + '
'+ + '
'+ + ''+ + '
' + ); + } else if (req.url == '/post') { + var form = new formidable.IncomingForm(), + fields = []; + + form + .on('error', function(err) { + res.writeHead(200, {'content-type': 'text/plain'}); + res.end('error:\n\n'+util.inspect(err)); + }) + .on('field', function(field, value) { + console.log(field, value); + fields.push([field, value]); + }) + .on('end', function() { + console.log('-> post done'); + res.writeHead(200, {'content-type': 'text/plain'}); + res.end('received fields:\n\n '+util.inspect(fields)); + }); + form.parse(req); + } else { + res.writeHead(404, {'content-type': 'text/plain'}); + res.end('404'); + } +}); +server.listen(TEST_PORT); + +console.log('listening on http://localhost:'+TEST_PORT+'/'); diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/example/upload.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/example/upload.js new file mode 100644 index 0000000..050cdd9 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/example/upload.js @@ -0,0 +1,48 @@ +require('../test/common'); +var http = require('http'), + util = require('util'), + formidable = require('formidable'), + server; + +server = http.createServer(function(req, res) { + if (req.url == '/') { + res.writeHead(200, {'content-type': 'text/html'}); + res.end( + '
'+ + '
'+ + '
'+ + ''+ + '
' + ); + } else if (req.url == '/upload') { + var form = new formidable.IncomingForm(), + files = [], + fields = []; + + form.uploadDir = TEST_TMP; + + form + .on('field', function(field, value) { + console.log(field, value); + fields.push([field, value]); + }) + .on('file', function(field, file) { + console.log(field, file); + files.push([field, file]); + }) + .on('end', function() { + console.log('-> upload done'); + res.writeHead(200, {'content-type': 'text/plain'}); + res.write('received fields:\n\n '+util.inspect(fields)); + res.write('\n\n'); + res.end('received files:\n\n '+util.inspect(files)); + }); + form.parse(req); + } else { + res.writeHead(404, {'content-type': 'text/plain'}); + res.end('404'); + } +}); +server.listen(TEST_PORT); + +console.log('listening on http://localhost:'+TEST_PORT+'/'); diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/index.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/index.js new file mode 100644 index 0000000..be41032 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/formidable'); \ No newline at end of file diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/file.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/file.js new file mode 100644 index 0000000..6dc8720 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/file.js @@ -0,0 +1,61 @@ +if (global.GENTLY) require = GENTLY.hijack(require); + +var util = require('./util'), + WriteStream = require('fs').WriteStream, + EventEmitter = require('events').EventEmitter; + +function File(properties) { + EventEmitter.call(this); + + this.size = 0; + this.path = null; + this.name = null; + this.type = null; + this.lastModifiedDate = null; + + this._writeStream = null; + + for (var key in properties) { + this[key] = properties[key]; + } + + this._backwardsCompatibility(); +} +module.exports = File; +util.inherits(File, EventEmitter); + +// @todo Next release: Show error messages when accessing these +File.prototype._backwardsCompatibility = function() { + var self = this; + this.__defineGetter__('length', function() { + return self.size; + }); + this.__defineGetter__('filename', function() { + return self.name; + }); + this.__defineGetter__('mime', function() { + return self.type; + }); +}; + +File.prototype.open = function() { + this._writeStream = new WriteStream(this.path); +}; + +File.prototype.write = function(buffer, cb) { + var self = this; + this._writeStream.write(buffer, function() { + self.lastModifiedDate = new Date(); + self.size += buffer.length; + self.emit('progress', self.size); + cb(); + }); +}; + +File.prototype.end = function(cb) { + var self = this; + this._writeStream.end(function() { + self.emit('end'); + cb(); + }); +}; diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/incoming_form.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/incoming_form.js new file mode 100644 index 0000000..62c9e3b --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/incoming_form.js @@ -0,0 +1,349 @@ +if (global.GENTLY) require = GENTLY.hijack(require); + +var util = require('./util'), + path = require('path'), + File = require('./file'), + MultipartParser = require('./multipart_parser').MultipartParser, + QuerystringParser = require('./querystring_parser').QuerystringParser, + StringDecoder = require('string_decoder').StringDecoder, + EventEmitter = require('events').EventEmitter; + +function IncomingForm() { + if (!(this instanceof IncomingForm)) return new IncomingForm; + EventEmitter.call(this); + + this.error = null; + this.ended = false; + + this.maxFieldsSize = 2 * 1024 * 1024; + this.keepExtensions = false; + this.uploadDir = '/tmp'; + this.encoding = 'utf-8'; + this.headers = null; + this.type = null; + + this.bytesReceived = null; + this.bytesExpected = null; + + this._parser = null; + this._flushing = 0; + this._fieldsSize = 0; +}; +util.inherits(IncomingForm, EventEmitter); +exports.IncomingForm = IncomingForm; + +IncomingForm.prototype.parse = function(req, cb) { + this.pause = function() { + try { + req.pause(); + } catch (err) { + // the stream was destroyed + if (!this.ended) { + // before it was completed, crash & burn + this._error(err); + } + return false; + } + return true; + }; + + this.resume = function() { + try { + req.resume(); + } catch (err) { + // the stream was destroyed + if (!this.ended) { + // before it was completed, crash & burn + this._error(err); + } + return false; + } + + return true; + }; + + this.writeHeaders(req.headers); + + var self = this; + req + .on('error', function(err) { + self._error(err); + }) + .on('aborted', function() { + self.emit('aborted'); + }) + .on('data', function(buffer) { + self.write(buffer); + }) + .on('end', function() { + if (self.error) { + return; + } + + var err = self._parser.end(); + if (err) { + self._error(err); + } + }); + + if (cb) { + var fields = {}, files = {}; + this + .on('field', function(name, value) { + fields[name] = value; + }) + .on('file', function(name, file) { + files[name] = file; + }) + .on('error', function(err) { + cb(err, fields, files); + }) + .on('end', function() { + cb(null, fields, files); + }); + } + + return this; +}; + +IncomingForm.prototype.writeHeaders = function(headers) { + this.headers = headers; + this._parseContentLength(); + this._parseContentType(); +}; + +IncomingForm.prototype.write = function(buffer) { + if (!this._parser) { + this._error(new Error('unintialized parser')); + return; + } + + this.bytesReceived += buffer.length; + this.emit('progress', this.bytesReceived, this.bytesExpected); + + var bytesParsed = this._parser.write(buffer); + if (bytesParsed !== buffer.length) { + this._error(new Error('parser error, '+bytesParsed+' of '+buffer.length+' bytes parsed')); + } + + return bytesParsed; +}; + +IncomingForm.prototype.pause = function() { + // this does nothing, unless overwritten in IncomingForm.parse + return false; +}; + +IncomingForm.prototype.resume = function() { + // this does nothing, unless overwritten in IncomingForm.parse + return false; +}; + +IncomingForm.prototype.onPart = function(part) { + // this method can be overwritten by the user + this.handlePart(part); +}; + +IncomingForm.prototype.handlePart = function(part) { + var self = this; + + if (!part.filename) { + var value = '' + , decoder = new StringDecoder(this.encoding); + + part.on('data', function(buffer) { + self._fieldsSize += buffer.length; + if (self._fieldsSize > self.maxFieldsSize) { + self._error(new Error('maxFieldsSize exceeded, received '+self._fieldsSize+' bytes of field data')); + return; + } + value += decoder.write(buffer); + }); + + part.on('end', function() { + self.emit('field', part.name, value); + }); + return; + } + + this._flushing++; + + var file = new File({ + path: this._uploadPath(part.filename), + name: part.filename, + type: part.mime, + }); + + this.emit('fileBegin', part.name, file); + + file.open(); + + part.on('data', function(buffer) { + self.pause(); + file.write(buffer, function() { + self.resume(); + }); + }); + + part.on('end', function() { + file.end(function() { + self._flushing--; + self.emit('file', part.name, file); + self._maybeEnd(); + }); + }); +}; + +IncomingForm.prototype._parseContentType = function() { + if (!this.headers['content-type']) { + this._error(new Error('bad content-type header, no content-type')); + return; + } + + if (this.headers['content-type'].match(/urlencoded/i)) { + this._initUrlencoded(); + return; + } + + if (this.headers['content-type'].match(/multipart/i)) { + var m; + if (m = this.headers['content-type'].match(/boundary=(?:"([^"]+)"|([^;]+))/i)) { + this._initMultipart(m[1] || m[2]); + } else { + this._error(new Error('bad content-type header, no multipart boundary')); + } + return; + } + + this._error(new Error('bad content-type header, unknown content-type: '+this.headers['content-type'])); +}; + +IncomingForm.prototype._error = function(err) { + if (this.error) { + return; + } + + this.error = err; + this.pause(); + this.emit('error', err); +}; + +IncomingForm.prototype._parseContentLength = function() { + if (this.headers['content-length']) { + this.bytesReceived = 0; + this.bytesExpected = parseInt(this.headers['content-length'], 10); + } +}; + +IncomingForm.prototype._newParser = function() { + return new MultipartParser(); +}; + +IncomingForm.prototype._initMultipart = function(boundary) { + this.type = 'multipart'; + + var parser = new MultipartParser(), + self = this, + headerField, + headerValue, + part; + + parser.initWithBoundary(boundary); + + parser.onPartBegin = function() { + part = new EventEmitter(); + part.headers = {}; + part.name = null; + part.filename = null; + part.mime = null; + headerField = ''; + headerValue = ''; + }; + + parser.onHeaderField = function(b, start, end) { + headerField += b.toString(self.encoding, start, end); + }; + + parser.onHeaderValue = function(b, start, end) { + headerValue += b.toString(self.encoding, start, end); + }; + + parser.onHeaderEnd = function() { + headerField = headerField.toLowerCase(); + part.headers[headerField] = headerValue; + + var m; + if (headerField == 'content-disposition') { + if (m = headerValue.match(/name="([^"]+)"/i)) { + part.name = m[1]; + } + + if (m = headerValue.match(/filename="([^;]+)"/i)) { + part.filename = m[1].substr(m[1].lastIndexOf('\\') + 1); + } + } else if (headerField == 'content-type') { + part.mime = headerValue; + } + + headerField = ''; + headerValue = ''; + }; + + parser.onHeadersEnd = function() { + self.onPart(part); + }; + + parser.onPartData = function(b, start, end) { + part.emit('data', b.slice(start, end)); + }; + + parser.onPartEnd = function() { + part.emit('end'); + }; + + parser.onEnd = function() { + self.ended = true; + self._maybeEnd(); + }; + + this._parser = parser; +}; + +IncomingForm.prototype._initUrlencoded = function() { + this.type = 'urlencoded'; + + var parser = new QuerystringParser() + , self = this; + + parser.onField = function(key, val) { + self.emit('field', key, val); + }; + + parser.onEnd = function() { + self.ended = true; + self._maybeEnd(); + }; + + this._parser = parser; +}; + +IncomingForm.prototype._uploadPath = function(filename) { + var name = ''; + for (var i = 0; i < 32; i++) { + name += Math.floor(Math.random() * 16).toString(16); + } + + if (this.keepExtensions) { + name += path.extname(filename); + } + + return path.join(this.uploadDir, name); +}; + +IncomingForm.prototype._maybeEnd = function() { + if (!this.ended || this._flushing) { + return; + } + + this.emit('end'); +}; diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/index.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/index.js new file mode 100644 index 0000000..7a6e3e1 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/index.js @@ -0,0 +1,3 @@ +var IncomingForm = require('./incoming_form').IncomingForm; +IncomingForm.IncomingForm = IncomingForm; +module.exports = IncomingForm; diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/multipart_parser.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/multipart_parser.js new file mode 100644 index 0000000..f553819 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/multipart_parser.js @@ -0,0 +1,303 @@ +var Buffer = require('buffer').Buffer, + s = 0, + S = + { PARSER_UNINITIALIZED: s++, + START: s++, + START_BOUNDARY: s++, + HEADER_FIELD_START: s++, + HEADER_FIELD: s++, + HEADER_VALUE_START: s++, + HEADER_VALUE: s++, + HEADER_VALUE_ALMOST_DONE: s++, + HEADERS_ALMOST_DONE: s++, + PART_DATA_START: s++, + PART_DATA: s++, + PART_END: s++, + END: s++, + }, + + f = 1, + F = + { PART_BOUNDARY: f, + LAST_BOUNDARY: f *= 2, + }, + + LF = 10, + CR = 13, + SPACE = 32, + HYPHEN = 45, + COLON = 58, + A = 97, + Z = 122, + + lower = function(c) { + return c | 0x20; + }; + +for (var s in S) { + exports[s] = S[s]; +} + +function MultipartParser() { + this.boundary = null; + this.boundaryChars = null; + this.lookbehind = null; + this.state = S.PARSER_UNINITIALIZED; + + this.index = null; + this.flags = 0; +}; +exports.MultipartParser = MultipartParser; + +MultipartParser.prototype.initWithBoundary = function(str) { + this.boundary = new Buffer(str.length+4); + this.boundary.write('\r\n--', 'ascii', 0); + this.boundary.write(str, 'ascii', 4); + this.lookbehind = new Buffer(this.boundary.length+8); + this.state = S.START; + + this.boundaryChars = {}; + for (var i = 0; i < this.boundary.length; i++) { + this.boundaryChars[this.boundary[i]] = true; + } +}; + +MultipartParser.prototype.write = function(buffer) { + var self = this, + i = 0, + len = buffer.length, + prevIndex = this.index, + index = this.index, + state = this.state, + flags = this.flags, + lookbehind = this.lookbehind, + boundary = this.boundary, + boundaryChars = this.boundaryChars, + boundaryLength = this.boundary.length, + boundaryEnd = boundaryLength - 1, + bufferLength = buffer.length, + c, + cl, + + mark = function(name) { + self[name+'Mark'] = i; + }, + clear = function(name) { + delete self[name+'Mark']; + }, + callback = function(name, buffer, start, end) { + if (start !== undefined && start === end) { + return; + } + + var callbackSymbol = 'on'+name.substr(0, 1).toUpperCase()+name.substr(1); + if (callbackSymbol in self) { + self[callbackSymbol](buffer, start, end); + } + }, + dataCallback = function(name, clear) { + var markSymbol = name+'Mark'; + if (!(markSymbol in self)) { + return; + } + + if (!clear) { + callback(name, buffer, self[markSymbol], buffer.length); + self[markSymbol] = 0; + } else { + callback(name, buffer, self[markSymbol], i); + delete self[markSymbol]; + } + }; + + for (i = 0; i < len; i++) { + c = buffer[i]; + switch (state) { + case S.PARSER_UNINITIALIZED: + return i; + case S.START: + index = 0; + state = S.START_BOUNDARY; + case S.START_BOUNDARY: + if (index == boundary.length - 2) { + if (c != CR) { + return i; + } + index++; + break; + } else if (index - 1 == boundary.length - 2) { + if (c != LF) { + return i; + } + index = 0; + callback('partBegin'); + state = S.HEADER_FIELD_START; + break; + } + + if (c != boundary[index+2]) { + return i; + } + index++; + break; + case S.HEADER_FIELD_START: + state = S.HEADER_FIELD; + mark('headerField'); + index = 0; + case S.HEADER_FIELD: + if (c == CR) { + clear('headerField'); + state = S.HEADERS_ALMOST_DONE; + break; + } + + index++; + if (c == HYPHEN) { + break; + } + + if (c == COLON) { + if (index == 1) { + // empty header field + return i; + } + dataCallback('headerField', true); + state = S.HEADER_VALUE_START; + break; + } + + cl = lower(c); + if (cl < A || cl > Z) { + return i; + } + break; + case S.HEADER_VALUE_START: + if (c == SPACE) { + break; + } + + mark('headerValue'); + state = S.HEADER_VALUE; + case S.HEADER_VALUE: + if (c == CR) { + dataCallback('headerValue', true); + callback('headerEnd'); + state = S.HEADER_VALUE_ALMOST_DONE; + } + break; + case S.HEADER_VALUE_ALMOST_DONE: + if (c != LF) { + return i; + } + state = S.HEADER_FIELD_START; + break; + case S.HEADERS_ALMOST_DONE: + if (c != LF) { + return i; + } + + callback('headersEnd'); + state = S.PART_DATA_START; + break; + case S.PART_DATA_START: + state = S.PART_DATA + mark('partData'); + case S.PART_DATA: + prevIndex = index; + + if (index == 0) { + // boyer-moore derrived algorithm to safely skip non-boundary data + while (i + boundaryLength <= bufferLength) { + if (buffer[i + boundaryEnd] in boundaryChars) { + break; + } + + i += boundaryLength; + } + c = buffer[i]; + } + + if (index < boundary.length) { + if (boundary[index] == c) { + if (index == 0) { + dataCallback('partData', true); + } + index++; + } else { + index = 0; + } + } else if (index == boundary.length) { + index++; + if (c == CR) { + // CR = part boundary + flags |= F.PART_BOUNDARY; + } else if (c == HYPHEN) { + // HYPHEN = end boundary + flags |= F.LAST_BOUNDARY; + } else { + index = 0; + } + } else if (index - 1 == boundary.length) { + if (flags & F.PART_BOUNDARY) { + index = 0; + if (c == LF) { + // unset the PART_BOUNDARY flag + flags &= ~F.PART_BOUNDARY; + callback('partEnd'); + callback('partBegin'); + state = S.HEADER_FIELD_START; + break; + } + } else if (flags & F.LAST_BOUNDARY) { + if (c == HYPHEN) { + callback('partEnd'); + callback('end'); + state = S.END; + } else { + index = 0; + } + } else { + index = 0; + } + } + + if (index > 0) { + // when matching a possible boundary, keep a lookbehind reference + // in case it turns out to be a false lead + lookbehind[index-1] = c; + } else if (prevIndex > 0) { + // if our boundary turned out to be rubbish, the captured lookbehind + // belongs to partData + callback('partData', lookbehind, 0, prevIndex); + prevIndex = 0; + mark('partData'); + + // reconsider the current character even so it interrupted the sequence + // it could be the beginning of a new sequence + i--; + } + + break; + case S.END: + break; + default: + return i; + } + } + + dataCallback('headerField'); + dataCallback('headerValue'); + dataCallback('partData'); + + this.index = index; + this.state = state; + this.flags = flags; + + return len; +}; + +MultipartParser.prototype.end = function() { + if (this.state != S.END) { + return new Error('MultipartParser.end(): stream ended unexpectedly'); + } +}; diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/querystring_parser.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/querystring_parser.js new file mode 100644 index 0000000..63f109e --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/querystring_parser.js @@ -0,0 +1,25 @@ +if (global.GENTLY) require = GENTLY.hijack(require); + +// This is a buffering parser, not quite as nice as the multipart one. +// If I find time I'll rewrite this to be fully streaming as well +var querystring = require('querystring'); + +function QuerystringParser() { + this.buffer = ''; +}; +exports.QuerystringParser = QuerystringParser; + +QuerystringParser.prototype.write = function(buffer) { + this.buffer += buffer.toString('ascii'); + return buffer.length; +}; + +QuerystringParser.prototype.end = function() { + var fields = querystring.parse(this.buffer); + for (var field in fields) { + this.onField(field, fields[field]); + } + this.buffer = ''; + + this.onEnd(); +}; \ No newline at end of file diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/util.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/util.js new file mode 100644 index 0000000..e9493e9 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/lib/util.js @@ -0,0 +1,6 @@ +// Backwards compatibility ... +try { + module.exports = require('util'); +} catch (e) { + module.exports = require('sys'); +} diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/package.json b/emacs.d/lisp/jshint-mode/node_modules/formidable/package.json new file mode 100644 index 0000000..c0ffdba --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/package.json @@ -0,0 +1,15 @@ +{ + "name": "formidable", + "version": "1.0.2", + "dependencies": {}, + "devDependencies": { + "gently": ">=0.7.0" + }, + "directories": { + "lib": "./lib" + }, + "main": "./lib/index", + "engines": { + "node": "*" + } +} \ No newline at end of file diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/test/common.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/test/common.js new file mode 100644 index 0000000..98de2b6 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/test/common.js @@ -0,0 +1,24 @@ +var path = require('path'), + fs = require('fs'); + +try { + global.Gently = require('gently'); +} catch (e) { + throw new Error('this test suite requires node-gently'); +} + +exports.lib = path.join(__dirname, '../lib'); + +global.GENTLY = new Gently(); + +global.assert = require('assert'); +global.TEST_PORT = 13532; +global.TEST_FIXTURES = path.join(__dirname, 'fixture'); +global.TEST_TMP = path.join(__dirname, 'tmp'); + +// Stupid new feature in node that complains about gently attaching too many +// listeners to process 'exit'. This is a workaround until I can think of a +// better way to deal with this. +if (process.setMaxListeners) { + process.setMaxListeners(10000); +} diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/test/fixture/multipart.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/test/fixture/multipart.js new file mode 100644 index 0000000..a476169 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/test/fixture/multipart.js @@ -0,0 +1,72 @@ +exports['rfc1867'] = + { boundary: 'AaB03x', + raw: + '--AaB03x\r\n'+ + 'content-disposition: form-data; name="field1"\r\n'+ + '\r\n'+ + 'Joe Blow\r\nalmost tricked you!\r\n'+ + '--AaB03x\r\n'+ + 'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n'+ + 'Content-Type: text/plain\r\n'+ + '\r\n'+ + '... contents of file1.txt ...\r\r\n'+ + '--AaB03x--\r\n', + parts: + [ { headers: { + 'content-disposition': 'form-data; name="field1"', + }, + data: 'Joe Blow\r\nalmost tricked you!', + }, + { headers: { + 'content-disposition': 'form-data; name="pics"; filename="file1.txt"', + 'Content-Type': 'text/plain', + }, + data: '... contents of file1.txt ...\r', + } + ] + }; + +exports['noTrailing\r\n'] = + { boundary: 'AaB03x', + raw: + '--AaB03x\r\n'+ + 'content-disposition: form-data; name="field1"\r\n'+ + '\r\n'+ + 'Joe Blow\r\nalmost tricked you!\r\n'+ + '--AaB03x\r\n'+ + 'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n'+ + 'Content-Type: text/plain\r\n'+ + '\r\n'+ + '... contents of file1.txt ...\r\r\n'+ + '--AaB03x--', + parts: + [ { headers: { + 'content-disposition': 'form-data; name="field1"', + }, + data: 'Joe Blow\r\nalmost tricked you!', + }, + { headers: { + 'content-disposition': 'form-data; name="pics"; filename="file1.txt"', + 'Content-Type': 'text/plain', + }, + data: '... contents of file1.txt ...\r', + } + ] + }; + +exports['emptyHeader'] = + { boundary: 'AaB03x', + raw: + '--AaB03x\r\n'+ + 'content-disposition: form-data; name="field1"\r\n'+ + ': foo\r\n'+ + '\r\n'+ + 'Joe Blow\r\nalmost tricked you!\r\n'+ + '--AaB03x\r\n'+ + 'content-disposition: form-data; name="pics"; filename="file1.txt"\r\n'+ + 'Content-Type: text/plain\r\n'+ + '\r\n'+ + '... contents of file1.txt ...\r\r\n'+ + '--AaB03x--\r\n', + expectError: true, + }; diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/test/integration/test-multipart-parser.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/test/integration/test-multipart-parser.js new file mode 100644 index 0000000..df93bc7 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/test/integration/test-multipart-parser.js @@ -0,0 +1,80 @@ +var common = require('../common'); +var CHUNK_LENGTH = 10, + multipartParser = require(common.lib + '/multipart_parser'), + MultipartParser = multipartParser.MultipartParser, + parser = new MultipartParser(), + fixtures = require('../fixture/multipart'), + Buffer = require('buffer').Buffer; + +Object.keys(fixtures).forEach(function(name) { + var fixture = fixtures[name], + buffer = new Buffer(Buffer.byteLength(fixture.raw, 'binary')), + offset = 0, + chunk, + nparsed, + + parts = [], + part = null, + headerField, + headerValue, + endCalled = ''; + + parser.initWithBoundary(fixture.boundary); + parser.onPartBegin = function() { + part = {headers: {}, data: ''}; + parts.push(part); + headerField = ''; + headerValue = ''; + }; + + parser.onHeaderField = function(b, start, end) { + headerField += b.toString('ascii', start, end); + }; + + parser.onHeaderValue = function(b, start, end) { + headerValue += b.toString('ascii', start, end); + } + + parser.onHeaderEnd = function() { + part.headers[headerField] = headerValue; + headerField = ''; + headerValue = ''; + }; + + parser.onPartData = function(b, start, end) { + var str = b.toString('ascii', start, end); + part.data += b.slice(start, end); + } + + parser.onEnd = function() { + endCalled = true; + } + + buffer.write(fixture.raw, 'binary', 0); + + while (offset < buffer.length) { + if (offset + CHUNK_LENGTH < buffer.length) { + chunk = buffer.slice(offset, offset+CHUNK_LENGTH); + } else { + chunk = buffer.slice(offset, buffer.length); + } + offset = offset + CHUNK_LENGTH; + + nparsed = parser.write(chunk); + if (nparsed != chunk.length) { + if (fixture.expectError) { + return; + } + puts('-- ERROR --'); + p(chunk.toString('ascii')); + throw new Error(chunk.length+' bytes written, but only '+nparsed+' bytes parsed!'); + } + } + + if (fixture.expectError) { + throw new Error('expected parse error did not happen'); + } + + assert.ok(endCalled); + assert.deepEqual(parts, fixture.parts); +}); diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/test/simple/test-file.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/test/simple/test-file.js new file mode 100644 index 0000000..52ceedb --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/test/simple/test-file.js @@ -0,0 +1,104 @@ +var common = require('../common'); +var WriteStreamStub = GENTLY.stub('fs', 'WriteStream'); + +var File = require(common.lib + '/file'), + EventEmitter = require('events').EventEmitter, + file, + gently; + +function test(test) { + gently = new Gently(); + file = new File(); + test(); + gently.verify(test.name); +} + +test(function constructor() { + assert.ok(file instanceof EventEmitter); + assert.strictEqual(file.size, 0); + assert.strictEqual(file.path, null); + assert.strictEqual(file.name, null); + assert.strictEqual(file.type, null); + assert.strictEqual(file.lastModifiedDate, null); + + assert.strictEqual(file._writeStream, null); + + (function testSetProperties() { + var file2 = new File({foo: 'bar'}); + assert.equal(file2.foo, 'bar'); + })(); +}); + +test(function open() { + var WRITE_STREAM; + file.path = '/foo'; + + gently.expect(WriteStreamStub, 'new', function (path) { + WRITE_STREAM = this; + assert.strictEqual(path, file.path); + }); + + file.open(); + assert.strictEqual(file._writeStream, WRITE_STREAM); +}); + +test(function write() { + var BUFFER = {length: 10}, + CB_STUB, + CB = function() { + CB_STUB.apply(this, arguments); + }; + + file._writeStream = {}; + + gently.expect(file._writeStream, 'write', function (buffer, cb) { + assert.strictEqual(buffer, BUFFER); + + gently.expect(file, 'emit', function (event, bytesWritten) { + assert.ok(file.lastModifiedDate instanceof Date); + assert.equal(event, 'progress'); + assert.equal(bytesWritten, file.size); + }); + + CB_STUB = gently.expect(function writeCb() { + assert.equal(file.size, 10); + }); + + cb(); + + gently.expect(file, 'emit', function (event, bytesWritten) { + assert.equal(event, 'progress'); + assert.equal(bytesWritten, file.size); + }); + + CB_STUB = gently.expect(function writeCb() { + assert.equal(file.size, 20); + }); + + cb(); + }); + + file.write(BUFFER, CB); +}); + +test(function end() { + var CB_STUB, + CB = function() { + CB_STUB.apply(this, arguments); + }; + + file._writeStream = {}; + + gently.expect(file._writeStream, 'end', function (cb) { + gently.expect(file, 'emit', function (event) { + assert.equal(event, 'end'); + }); + + CB_STUB = gently.expect(function endCb() { + }); + + cb(); + }); + + file.end(CB); +}); diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/test/simple/test-incoming-form.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/test/simple/test-incoming-form.js new file mode 100644 index 0000000..0945abd --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/test/simple/test-incoming-form.js @@ -0,0 +1,710 @@ +var common = require('../common'); +var MultipartParserStub = GENTLY.stub('./multipart_parser', 'MultipartParser'), + QuerystringParserStub = GENTLY.stub('./querystring_parser', 'QuerystringParser'), + EventEmitterStub = GENTLY.stub('events', 'EventEmitter'), + FileStub = GENTLY.stub('./file'); + +var formidable = require(common.lib + '/index'), + IncomingForm = formidable.IncomingForm, + events = require('events'), + fs = require('fs'), + path = require('path'), + Buffer = require('buffer').Buffer, + fixtures = require('../fixture/multipart'), + form, + gently; + +function test(test) { + gently = new Gently(); + gently.expect(EventEmitterStub, 'call'); + form = new IncomingForm(); + test(); + gently.verify(test.name); +} + +test(function constructor() { + assert.strictEqual(form.error, null); + assert.strictEqual(form.ended, false); + assert.strictEqual(form.type, null); + assert.strictEqual(form.headers, null); + assert.strictEqual(form.keepExtensions, false); + assert.strictEqual(form.uploadDir, '/tmp'); + assert.strictEqual(form.encoding, 'utf-8'); + assert.strictEqual(form.bytesReceived, null); + assert.strictEqual(form.bytesExpected, null); + assert.strictEqual(form.maxFieldsSize, 2 * 1024 * 1024); + assert.strictEqual(form._parser, null); + assert.strictEqual(form._flushing, 0); + assert.strictEqual(form._fieldsSize, 0); + assert.ok(form instanceof EventEmitterStub); + assert.equal(form.constructor.name, 'IncomingForm'); + + (function testSimpleConstructor() { + gently.expect(EventEmitterStub, 'call'); + var form = IncomingForm(); + assert.ok(form instanceof IncomingForm); + })(); + + (function testSimpleConstructorShortcut() { + gently.expect(EventEmitterStub, 'call'); + var form = formidable(); + assert.ok(form instanceof IncomingForm); + })(); +}); + +test(function parse() { + var REQ = {headers: {}} + , emit = {}; + + gently.expect(form, 'writeHeaders', function(headers) { + assert.strictEqual(headers, REQ.headers); + }); + + var events = ['error', 'aborted', 'data', 'end']; + gently.expect(REQ, 'on', events.length, function(event, fn) { + assert.equal(event, events.shift()); + emit[event] = fn; + return this; + }); + + form.parse(REQ); + + (function testPause() { + gently.expect(REQ, 'pause'); + assert.strictEqual(form.pause(), true); + })(); + + (function testPauseCriticalException() { + form.ended = false; + + var ERR = new Error('dasdsa'); + gently.expect(REQ, 'pause', function() { + throw ERR; + }); + + gently.expect(form, '_error', function(err) { + assert.strictEqual(err, ERR); + }); + + assert.strictEqual(form.pause(), false); + })(); + + (function testPauseHarmlessException() { + form.ended = true; + + var ERR = new Error('dasdsa'); + gently.expect(REQ, 'pause', function() { + throw ERR; + }); + + assert.strictEqual(form.pause(), false); + })(); + + (function testResume() { + gently.expect(REQ, 'resume'); + assert.strictEqual(form.resume(), true); + })(); + + (function testResumeCriticalException() { + form.ended = false; + + var ERR = new Error('dasdsa'); + gently.expect(REQ, 'resume', function() { + throw ERR; + }); + + gently.expect(form, '_error', function(err) { + assert.strictEqual(err, ERR); + }); + + assert.strictEqual(form.resume(), false); + })(); + + (function testResumeHarmlessException() { + form.ended = true; + + var ERR = new Error('dasdsa'); + gently.expect(REQ, 'resume', function() { + throw ERR; + }); + + assert.strictEqual(form.resume(), false); + })(); + + (function testEmitError() { + var ERR = new Error('something bad happened'); + gently.expect(form, '_error',function(err) { + assert.strictEqual(err, ERR); + }); + emit.error(ERR); + })(); + + (function testEmitAborted() { + gently.expect(form, 'emit',function(event) { + assert.equal(event, 'aborted'); + }); + + emit.aborted(); + })(); + + + (function testEmitData() { + var BUFFER = [1, 2, 3]; + gently.expect(form, 'write', function(buffer) { + assert.strictEqual(buffer, BUFFER); + }); + emit.data(BUFFER); + })(); + + (function testEmitEnd() { + form._parser = {}; + + (function testWithError() { + var ERR = new Error('haha'); + gently.expect(form._parser, 'end', function() { + return ERR; + }); + + gently.expect(form, '_error', function(err) { + assert.strictEqual(err, ERR); + }); + + emit.end(); + })(); + + (function testWithoutError() { + gently.expect(form._parser, 'end'); + emit.end(); + })(); + + (function testAfterError() { + form.error = true; + emit.end(); + })(); + })(); + + (function testWithCallback() { + gently.expect(EventEmitterStub, 'call'); + var form = new IncomingForm(), + REQ = {headers: {}}, + parseCalled = 0; + + gently.expect(form, 'writeHeaders'); + gently.expect(REQ, 'on', 4, function() { + return this; + }); + + gently.expect(form, 'on', 4, function(event, fn) { + if (event == 'field') { + fn('field1', 'foo'); + fn('field1', 'bar'); + fn('field2', 'nice'); + } + + if (event == 'file') { + fn('file1', '1'); + fn('file1', '2'); + fn('file2', '3'); + } + + if (event == 'end') { + fn(); + } + return this; + }); + + form.parse(REQ, gently.expect(function parseCbOk(err, fields, files) { + assert.deepEqual(fields, {field1: 'bar', field2: 'nice'}); + assert.deepEqual(files, {file1: '2', file2: '3'}); + })); + + gently.expect(form, 'writeHeaders'); + gently.expect(REQ, 'on', 4, function() { + return this; + }); + + var ERR = new Error('test'); + gently.expect(form, 'on', 3, function(event, fn) { + if (event == 'field') { + fn('foo', 'bar'); + } + + if (event == 'error') { + fn(ERR); + gently.expect(form, 'on'); + } + return this; + }); + + form.parse(REQ, gently.expect(function parseCbErr(err, fields, files) { + assert.strictEqual(err, ERR); + assert.deepEqual(fields, {foo: 'bar'}); + })); + })(); +}); + +test(function pause() { + assert.strictEqual(form.pause(), false); +}); + +test(function resume() { + assert.strictEqual(form.resume(), false); +}); + + +test(function writeHeaders() { + var HEADERS = {}; + gently.expect(form, '_parseContentLength'); + gently.expect(form, '_parseContentType'); + + form.writeHeaders(HEADERS); + assert.strictEqual(form.headers, HEADERS); +}); + +test(function write() { + var parser = {}, + BUFFER = [1, 2, 3]; + + form._parser = parser; + form.bytesExpected = 523423; + + (function testBasic() { + gently.expect(form, 'emit', function(event, bytesReceived, bytesExpected) { + assert.equal(event, 'progress'); + assert.equal(bytesReceived, BUFFER.length); + assert.equal(bytesExpected, form.bytesExpected); + }); + + gently.expect(parser, 'write', function(buffer) { + assert.strictEqual(buffer, BUFFER); + return buffer.length; + }); + + assert.equal(form.write(BUFFER), BUFFER.length); + assert.equal(form.bytesReceived, BUFFER.length); + })(); + + (function testParserError() { + gently.expect(form, 'emit'); + + gently.expect(parser, 'write', function(buffer) { + assert.strictEqual(buffer, BUFFER); + return buffer.length - 1; + }); + + gently.expect(form, '_error', function(err) { + assert.ok(err.message.match(/parser error/i)); + }); + + assert.equal(form.write(BUFFER), BUFFER.length - 1); + assert.equal(form.bytesReceived, BUFFER.length + BUFFER.length); + })(); + + (function testUninitialized() { + delete form._parser; + + gently.expect(form, '_error', function(err) { + assert.ok(err.message.match(/unintialized parser/i)); + }); + form.write(BUFFER); + })(); +}); + +test(function parseContentType() { + var HEADERS = {}; + + form.headers = {'content-type': 'application/x-www-form-urlencoded'}; + gently.expect(form, '_initUrlencoded'); + form._parseContentType(); + + // accept anything that has 'urlencoded' in it + form.headers = {'content-type': 'broken-client/urlencoded-stupid'}; + gently.expect(form, '_initUrlencoded'); + form._parseContentType(); + + var BOUNDARY = '---------------------------57814261102167618332366269'; + form.headers = {'content-type': 'multipart/form-data; boundary='+BOUNDARY}; + + gently.expect(form, '_initMultipart', function(boundary) { + assert.equal(boundary, BOUNDARY); + }); + form._parseContentType(); + + (function testQuotedBoundary() { + form.headers = {'content-type': 'multipart/form-data; boundary="' + BOUNDARY + '"'}; + + gently.expect(form, '_initMultipart', function(boundary) { + assert.equal(boundary, BOUNDARY); + }); + form._parseContentType(); + })(); + + (function testNoBoundary() { + form.headers = {'content-type': 'multipart/form-data'}; + + gently.expect(form, '_error', function(err) { + assert.ok(err.message.match(/no multipart boundary/i)); + }); + form._parseContentType(); + })(); + + (function testNoContentType() { + form.headers = {}; + + gently.expect(form, '_error', function(err) { + assert.ok(err.message.match(/no content-type/i)); + }); + form._parseContentType(); + })(); + + (function testUnknownContentType() { + form.headers = {'content-type': 'invalid'}; + + gently.expect(form, '_error', function(err) { + assert.ok(err.message.match(/unknown content-type/i)); + }); + form._parseContentType(); + })(); +}); + +test(function parseContentLength() { + var HEADERS = {}; + + form.headers = {}; + form._parseContentLength(); + assert.strictEqual(form.bytesExpected, null); + + form.headers['content-length'] = '8'; + form._parseContentLength(); + assert.strictEqual(form.bytesReceived, 0); + assert.strictEqual(form.bytesExpected, 8); + + // JS can be evil, lets make sure we are not + form.headers['content-length'] = '08'; + form._parseContentLength(); + assert.strictEqual(form.bytesExpected, 8); +}); + +test(function _initMultipart() { + var BOUNDARY = '123', + PARSER; + + gently.expect(MultipartParserStub, 'new', function() { + PARSER = this; + }); + + gently.expect(MultipartParserStub.prototype, 'initWithBoundary', function(boundary) { + assert.equal(boundary, BOUNDARY); + }); + + form._initMultipart(BOUNDARY); + assert.equal(form.type, 'multipart'); + assert.strictEqual(form._parser, PARSER); + + (function testRegularField() { + var PART; + gently.expect(EventEmitterStub, 'new', function() { + PART = this; + }); + + gently.expect(form, 'onPart', function(part) { + assert.strictEqual(part, PART); + assert.deepEqual + ( part.headers + , { 'content-disposition': 'form-data; name="field1"' + , 'foo': 'bar' + } + ); + assert.equal(part.name, 'field1'); + + var strings = ['hello', ' world']; + gently.expect(part, 'emit', 2, function(event, b) { + assert.equal(event, 'data'); + assert.equal(b.toString(), strings.shift()); + }); + + gently.expect(part, 'emit', function(event, b) { + assert.equal(event, 'end'); + }); + }); + + PARSER.onPartBegin(); + PARSER.onHeaderField(new Buffer('content-disposition'), 0, 10); + PARSER.onHeaderField(new Buffer('content-disposition'), 10, 19); + PARSER.onHeaderValue(new Buffer('form-data; name="field1"'), 0, 14); + PARSER.onHeaderValue(new Buffer('form-data; name="field1"'), 14, 24); + PARSER.onHeaderEnd(); + PARSER.onHeaderField(new Buffer('foo'), 0, 3); + PARSER.onHeaderValue(new Buffer('bar'), 0, 3); + PARSER.onHeaderEnd(); + PARSER.onHeadersEnd(); + PARSER.onPartData(new Buffer('hello world'), 0, 5); + PARSER.onPartData(new Buffer('hello world'), 5, 11); + PARSER.onPartEnd(); + })(); + + (function testFileField() { + var PART; + gently.expect(EventEmitterStub, 'new', function() { + PART = this; + }); + + gently.expect(form, 'onPart', function(part) { + assert.deepEqual + ( part.headers + , { 'content-disposition': 'form-data; name="field2"; filename="C:\\Documents and Settings\\IE\\Must\\Die\\Sun"et.jpg"' + , 'content-type': 'text/plain' + } + ); + assert.equal(part.name, 'field2'); + assert.equal(part.filename, 'Sun"et.jpg'); + assert.equal(part.mime, 'text/plain'); + + gently.expect(part, 'emit', function(event, b) { + assert.equal(event, 'data'); + assert.equal(b.toString(), '... contents of file1.txt ...'); + }); + + gently.expect(part, 'emit', function(event, b) { + assert.equal(event, 'end'); + }); + }); + + PARSER.onPartBegin(); + PARSER.onHeaderField(new Buffer('content-disposition'), 0, 19); + PARSER.onHeaderValue(new Buffer('form-data; name="field2"; filename="C:\\Documents and Settings\\IE\\Must\\Die\\Sun"et.jpg"'), 0, 85); + PARSER.onHeaderEnd(); + PARSER.onHeaderField(new Buffer('Content-Type'), 0, 12); + PARSER.onHeaderValue(new Buffer('text/plain'), 0, 10); + PARSER.onHeaderEnd(); + PARSER.onHeadersEnd(); + PARSER.onPartData(new Buffer('... contents of file1.txt ...'), 0, 29); + PARSER.onPartEnd(); + })(); + + (function testEnd() { + gently.expect(form, '_maybeEnd'); + PARSER.onEnd(); + assert.ok(form.ended); + })(); +}); + +test(function _initUrlencoded() { + var PARSER; + + gently.expect(QuerystringParserStub, 'new', function() { + PARSER = this; + }); + + form._initUrlencoded(); + assert.equal(form.type, 'urlencoded'); + assert.strictEqual(form._parser, PARSER); + + (function testOnField() { + var KEY = 'KEY', VAL = 'VAL'; + gently.expect(form, 'emit', function(field, key, val) { + assert.equal(field, 'field'); + assert.equal(key, KEY); + assert.equal(val, VAL); + }); + + PARSER.onField(KEY, VAL); + })(); + + (function testOnEnd() { + gently.expect(form, '_maybeEnd'); + + PARSER.onEnd(); + assert.equal(form.ended, true); + })(); +}); + +test(function _error() { + var ERR = new Error('bla'); + + gently.expect(form, 'pause'); + gently.expect(form, 'emit', function(event, err) { + assert.equal(event, 'error'); + assert.strictEqual(err, ERR); + }); + + form._error(ERR); + assert.strictEqual(form.error, ERR); + + // make sure _error only does its thing once + form._error(ERR); +}); + +test(function onPart() { + var PART = {}; + gently.expect(form, 'handlePart', function(part) { + assert.strictEqual(part, PART); + }); + + form.onPart(PART); +}); + +test(function handlePart() { + (function testUtf8Field() { + var PART = new events.EventEmitter(); + PART.name = 'my_field'; + + gently.expect(form, 'emit', function(event, field, value) { + assert.equal(event, 'field'); + assert.equal(field, 'my_field'); + assert.equal(value, 'hello world: €'); + }); + + form.handlePart(PART); + PART.emit('data', new Buffer('hello')); + PART.emit('data', new Buffer(' world: ')); + PART.emit('data', new Buffer([0xE2])); + PART.emit('data', new Buffer([0x82, 0xAC])); + PART.emit('end'); + })(); + + (function testBinaryField() { + var PART = new events.EventEmitter(); + PART.name = 'my_field2'; + + gently.expect(form, 'emit', function(event, field, value) { + assert.equal(event, 'field'); + assert.equal(field, 'my_field2'); + assert.equal(value, 'hello world: '+new Buffer([0xE2, 0x82, 0xAC]).toString('binary')); + }); + + form.encoding = 'binary'; + form.handlePart(PART); + PART.emit('data', new Buffer('hello')); + PART.emit('data', new Buffer(' world: ')); + PART.emit('data', new Buffer([0xE2])); + PART.emit('data', new Buffer([0x82, 0xAC])); + PART.emit('end'); + })(); + + (function testFieldSize() { + form.maxFieldsSize = 8; + var PART = new events.EventEmitter(); + PART.name = 'my_field'; + + gently.expect(form, '_error', function(err) { + assert.equal(err.message, 'maxFieldsSize exceeded, received 9 bytes of field data'); + }); + + form.handlePart(PART); + form._fieldsSize = 1; + PART.emit('data', new Buffer(7)); + PART.emit('data', new Buffer(1)); + })(); + + (function testFilePart() { + var PART = new events.EventEmitter(), + FILE = new events.EventEmitter(), + PATH = '/foo/bar'; + + PART.name = 'my_file'; + PART.filename = 'sweet.txt'; + PART.mime = 'sweet.txt'; + + gently.expect(form, '_uploadPath', function(filename) { + assert.equal(filename, PART.filename); + return PATH; + }); + + gently.expect(FileStub, 'new', function(properties) { + assert.equal(properties.path, PATH); + assert.equal(properties.name, PART.filename); + assert.equal(properties.type, PART.mime); + FILE = this; + + gently.expect(form, 'emit', function (event, field, file) { + assert.equal(event, 'fileBegin'); + assert.strictEqual(field, PART.name); + assert.strictEqual(file, FILE); + }); + + gently.expect(FILE, 'open'); + }); + + form.handlePart(PART); + assert.equal(form._flushing, 1); + + var BUFFER; + gently.expect(form, 'pause'); + gently.expect(FILE, 'write', function(buffer, cb) { + assert.strictEqual(buffer, BUFFER); + gently.expect(form, 'resume'); + // @todo handle cb(new Err) + cb(); + }); + + PART.emit('data', BUFFER = new Buffer('test')); + + gently.expect(FILE, 'end', function(cb) { + gently.expect(form, 'emit', function(event, field, file) { + assert.equal(event, 'file'); + assert.strictEqual(file, FILE); + }); + + gently.expect(form, '_maybeEnd'); + + cb(); + assert.equal(form._flushing, 0); + }); + + PART.emit('end'); + })(); +}); + +test(function _uploadPath() { + (function testUniqueId() { + var UUID_A, UUID_B; + gently.expect(GENTLY.hijacked.path, 'join', function(uploadDir, uuid) { + assert.equal(uploadDir, form.uploadDir); + UUID_A = uuid; + }); + form._uploadPath(); + + gently.expect(GENTLY.hijacked.path, 'join', function(uploadDir, uuid) { + UUID_B = uuid; + }); + form._uploadPath(); + + assert.notEqual(UUID_A, UUID_B); + })(); + + (function testFileExtension() { + form.keepExtensions = true; + var FILENAME = 'foo.jpg', + EXT = '.bar'; + + gently.expect(GENTLY.hijacked.path, 'extname', function(filename) { + assert.equal(filename, FILENAME); + gently.restore(path, 'extname'); + + return EXT; + }); + + gently.expect(GENTLY.hijacked.path, 'join', function(uploadDir, name) { + assert.equal(path.extname(name), EXT); + }); + form._uploadPath(FILENAME); + })(); +}); + +test(function _maybeEnd() { + gently.expect(form, 'emit', 0); + form._maybeEnd(); + + form.ended = true; + form._flushing = 1; + form._maybeEnd(); + + gently.expect(form, 'emit', function(event) { + assert.equal(event, 'end'); + }); + + form.ended = true; + form._flushing = 0; + form._maybeEnd(); +}); diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/test/simple/test-multipart-parser.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/test/simple/test-multipart-parser.js new file mode 100644 index 0000000..445054d --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/test/simple/test-multipart-parser.js @@ -0,0 +1,50 @@ +var common = require('../common'); +var multipartParser = require(common.lib + '/multipart_parser'), + MultipartParser = multipartParser.MultipartParser, + events = require('events'), + Buffer = require('buffer').Buffer, + parser; + +function test(test) { + parser = new MultipartParser(); + test(); +} + +test(function constructor() { + assert.equal(parser.boundary, null); + assert.equal(parser.state, 0); + assert.equal(parser.flags, 0); + assert.equal(parser.boundaryChars, null); + assert.equal(parser.index, null); + assert.equal(parser.lookbehind, null); + assert.equal(parser.constructor.name, 'MultipartParser'); +}); + +test(function initWithBoundary() { + var boundary = 'abc'; + parser.initWithBoundary(boundary); + assert.deepEqual(Array.prototype.slice.call(parser.boundary), [13, 10, 45, 45, 97, 98, 99]); + assert.equal(parser.state, multipartParser.START); + + assert.deepEqual(parser.boundaryChars, {10: true, 13: true, 45: true, 97: true, 98: true, 99: true}); +}); + +test(function parserError() { + var boundary = 'abc', + buffer = new Buffer(5); + + parser.initWithBoundary(boundary); + buffer.write('--ad', 'ascii', 0); + assert.equal(parser.write(buffer), 3); +}); + +test(function end() { + (function testError() { + assert.equal(parser.end().message, 'MultipartParser.end(): stream ended unexpectedly'); + })(); + + (function testRegular() { + parser.state = multipartParser.END; + assert.strictEqual(parser.end(), undefined); + })(); +}); diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/test/simple/test-querystring-parser.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/test/simple/test-querystring-parser.js new file mode 100644 index 0000000..54d3e2d --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/test/simple/test-querystring-parser.js @@ -0,0 +1,45 @@ +var common = require('../common'); +var QuerystringParser = require(common.lib + '/querystring_parser').QuerystringParser, + Buffer = require('buffer').Buffer, + gently, + parser; + +function test(test) { + gently = new Gently(); + parser = new QuerystringParser(); + test(); + gently.verify(test.name); +} + +test(function constructor() { + assert.equal(parser.buffer, ''); + assert.equal(parser.constructor.name, 'QuerystringParser'); +}); + +test(function write() { + var a = new Buffer('a=1'); + assert.equal(parser.write(a), a.length); + + var b = new Buffer('&b=2'); + parser.write(b); + assert.equal(parser.buffer, a + b); +}); + +test(function end() { + var FIELDS = {a: ['b', {c: 'd'}], e: 'f'}; + + gently.expect(GENTLY.hijacked.querystring, 'parse', function(str) { + assert.equal(str, parser.buffer); + return FIELDS; + }); + + gently.expect(parser, 'onField', Object.keys(FIELDS).length, function(key, val) { + assert.deepEqual(FIELDS[key], val); + }); + + gently.expect(parser, 'onEnd'); + + parser.buffer = 'my buffer'; + parser.end(); + assert.equal(parser.buffer, ''); +}); diff --git a/emacs.d/lisp/jshint-mode/node_modules/formidable/test/system/test-multi-video-upload.js b/emacs.d/lisp/jshint-mode/node_modules/formidable/test/system/test-multi-video-upload.js new file mode 100644 index 0000000..fcfdb94 --- /dev/null +++ b/emacs.d/lisp/jshint-mode/node_modules/formidable/test/system/test-multi-video-upload.js @@ -0,0 +1,72 @@ +var common = require('../common'); +var BOUNDARY = '---------------------------10102754414578508781458777923', + FIXTURE = TEST_FIXTURES+'/multi_video.upload', + fs = require('fs'), + util = require(common.lib + '/util'), + http = require('http'), + formidable = require(common.lib + '/index'), + server = http.createServer(); + +server.on('request', function(req, res) { + var form = new formidable.IncomingForm(), + uploads = {}; + + form.uploadDir = TEST_TMP; + form.parse(req); + + form + .on('fileBegin', function(field, file) { + assert.equal(field, 'upload'); + + var tracker = {file: file, progress: [], ended: false}; + uploads[file.filename] = tracker; + file + .on('progress', function(bytesReceived) { + tracker.progress.push(bytesReceived); + assert.equal(bytesReceived, file.length); + }) + .on('end', function() { + tracker.ended = true; + }); + }) + .on('field', function(field, value) { + assert.equal(field, 'title'); + assert.equal(value, ''); + }) + .on('file', function(field, file) { + assert.equal(field, 'upload'); + assert.strictEqual(uploads[file.filename].file, file); + }) + .on('end', function() { + assert.ok(uploads['shortest_video.flv']); + assert.ok(uploads['shortest_video.flv'].ended); + assert.ok(uploads['shortest_video.flv'].progress.length > 3); + assert.equal(uploads['shortest_video.flv'].progress.slice(-1), uploads['shortest_video.flv'].file.length); + assert.ok(uploads['shortest_video.mp4']); + assert.ok(uploads['shortest_video.mp4'].ended); + assert.ok(uploads['shortest_video.mp4'].progress.length > 3); + + server.close(); + res.writeHead(200); + res.end('good'); + }); +}); + +server.listen(TEST_PORT, function() { + var client = http.createClient(TEST_PORT), + stat = fs.statSync(FIXTURE), + headers = { + 'content-type': 'multipart/form-data; boundary='+BOUNDARY, + 'content-length': stat.size, + } + request = client.request('POST', '/', headers), + fixture = new fs.ReadStream(FIXTURE); + + fixture + .on('data', function(b) { + request.write(b); + }) + .on('end', function() { + request.end(); + }); +}); diff --git a/emacs.d/lisp/jshint-mode/package.json b/emacs.d/lisp/jshint-mode/package.json new file mode 100644 index 0000000..d6f223e --- /dev/null +++ b/emacs.d/lisp/jshint-mode/package.json @@ -0,0 +1,15 @@ +{ + "name": "jshint-mode", + "version": "0.0.2", + "description": "JSHint", + "homepage": "http://github.com/daleharvey/jshint-mode", + "author": { + "name": "Dale Harvey", + "url": "http://arandomurl.com/" + }, + "dependencies": { + "formidable": ">=0.9.11" + }, + "engines": ["node >= 0.2.0"], + "bin": { "jshint-emacs-conf" : "./jshint-emacs.js" } +} \ No newline at end of file diff --git a/emacs.d/lisp/promela-mode.el b/emacs.d/lisp/promela-mode.el new file mode 100644 index 0000000..10cac3f --- /dev/null +++ b/emacs.d/lisp/promela-mode.el @@ -0,0 +1,985 @@ +;; promela-mode.el --- major mode for editing PROMELA program files +;; $Revision: 1.11 $ $Date: 2001/07/09 18:36:45 $ $Author: engstrom $ + +;; Author: Eric Engstrom +;; Maintainer: Eric Engstrom +;; Keywords: spin, promela, tools + +;; Copyright (C) 1998-2003 Eric Engstrom / Honeywell Laboratories + +;; ... Possibly insert GPL here someday ... + +;;; Commentary: + +;; This file contains code for a GNU Emacs major mode for editing +;; PROMELA (SPIN) program files. + +;; Type "C-h m" in Emacs (while in a buffer in promela-mode) for +;; information on how to configure indentation and fontification, +;; or look at the configuration variables below. + +;; To use, place promela-mode.el in a directory in your load-path. +;; Then, put the following lines into your .emacs and promela-mode +;; will be automatically loaded when editing a PROMELA program. + +;; (autoload 'promela-mode "promela-mode" "PROMELA mode" nil t) +;; (setq auto-mode-alist +;; (append +;; (list (cons "\\.promela$" 'promela-mode) +;; (cons "\\.spin$" 'promela-mode) +;; (cons "\\.pml$" 'promela-mode) +;; ;; (cons "\\.other-extensions$" 'promela-mode) +;; ) +;; auto-mode-alist)) + +;; If you wish for promela-mode to be used for files with other +;; extensions you add your own patterned after the code above. + +;; Note that promela-mode adhears to the font-lock "standards" and +;; defines several "levels" of fontification or colorization. The +;; default is fairly gaudy, so I can imagine that some folks would +;; like a bit less. FMI: see `font-lock-maximum-decoration' + +;; This mode is known to work under the following versions of emacs: +;; - XEmacs: 19.16, 20.x, 21.x +;; - FSF/GNU Emacs: 19.34 +;; - NTEmacs (FSF): 20.[67] +;; That is not to say there are no bugs specific to one of those versions :-) + +;; Please send any comments, bugs, patches or other requests to +;; Eric Engstrom at engstrom@htc.honeywell.com + +;; To-Do: +;; - compile/syntax-check/verify? (suggested by R.Goldman) +;; - indentation - splitting lines at logical operators (M. Rangarajan) +;; [ This might "devolve" to indentation after "->" or ";" +;; being as is, but anything else indent even more? ] +;; :: SomeReallyLongArrayRef[this].typedefField != SomeReallyLongConstant -> /* some-comment */ +;; [ Suggestion would be to break the first line after the !=, therefore: ] +;; :: SomeReallyLongArrayRef[this].typedefField +;; != SomeReallyLongConstant -> /* some-comment */ +;; [ at this point I'm not so sure about this change... EE: 2001/05/19 ] + +;;; ------------------------------------------------------------------------- +;;; Code: + +;; NOTE: same as CVS revision: +(defconst promela-mode-version "$Revision: 1.11 $" + "Promela-mode version number.") + +;; ------------------------------------------------------------------------- +;; The following constant values can be modified by the user in a .emacs file + +(defconst promela-block-indent 2 + "*Controls indentation of lines within a block (`{') construct") + +(defconst promela-selection-indent 2 + "*Controls indentation of options within a selection (`if') +or iteration (`do') construct") + +(defconst promela-selection-option-indent 3 + "*Controls indentation of lines after options within selection or +iteration construct (`::')") + +(defconst promela-comment-col 32 + "*Defines the desired comment column for comments to the right of text.") + +(defconst promela-tab-always-indent t + "*Non-nil means TAB in Promela mode should always reindent the current line, +regardless of where in the line point is when the TAB command is used.") + +(defconst promela-auto-match-delimiter t + "*Non-nil means typing an open-delimiter (i.e. parentheses, brace, quote, etc) +should also insert the matching closing delmiter character.") + +;; That should be about it for most users... +;; unless you wanna hack elisp, the rest of this is probably uninteresting + + +;; ------------------------------------------------------------------------- +;; help determine what emacs we have here... + +(defconst promela-xemacsp (string-match "XEmacs" (emacs-version)) + "Non-nil if we are running in the XEmacs environment.") + +;;;(defconst promela-xemacs20p (and promela-xemacsp (>= emacs-major-version 20)) +;; "Non-nil if we are running in an XEmacs environment version 20 or greater.") + +;; ------------------------------------------------------------------------- +;; promela-mode font faces/definitions + +;; make use of font-lock stuff, so say that explicitly +(require 'font-lock) + +;; BLECH! YUCK! I just wish these guys could agree to something.... +;; Faces available in: ntemacs emacs xemacs xemacs xemacs +;; font-lock- xxx -face 20.6 19.34 19.16 20.x 21.x +;; -builtin- X +;; -constant- X +;; -comment- X X X X X +;; -doc-string- X X X +;; -function-name- X X X X X +;; -keyword- X X X X X +;; -preprocessor- X X X +;; -reference- X X X X +;; -signal-name- X X!20.0 +;; -string- X X X X X +;; -type- X X X X X +;; -variable-name- X X X X X +;; -warning- X X + +;;; Compatibility on faces between versions of emacs-en +(unless promela-xemacsp + + (defvar font-lock-preprocessor-face 'font-lock-preprocessor-face + "Face name to use for preprocessor statements.") + ;; For consistency try to define the preprocessor face == builtin face + (condition-case nil + (copy-face 'font-lock-builtin-face 'font-lock-preprocessor-face) + (error + (defface font-lock-preprocessor-face + '((t (:foreground "blue" :italic nil :underline t))) + "Face Lock mode face used to highlight preprocessor statements." + :group 'font-lock-highlighting-faces))) + + (defvar font-lock-reference-face 'font-lock-reference-face + "Face name to use for constants and reference and label names.") + ;; For consistency try to define the reference face == constant face + (condition-case nil + (copy-face 'font-lock-constant-face 'font-lock-reference-face) + (error + (defface font-lock-reference-face + '((((class grayscale) (background light)) + (:foreground "LightGray" :bold t :underline t)) + (((class grayscale) (background dark)) + (:foreground "Gray50" :bold t :underline t)) + (((class color) (background light)) (:foreground "CadetBlue")) + (((class color) (background dark)) (:foreground "Aquamarine")) + (t (:bold t :underline t))) + "Font Lock mode face used to highlight constancs, references and labels." + :group 'font-lock-highlighting-faces))) + + ) + +;; send-poll "symbol" face is custom to promela-mode +;; but check for existence to allow someone to override it +(defvar promela-fl-send-poll-face 'promela-fl-send-poll-face + "Face name to use for Promela Send or Poll symbols: `!' or `?'") +(copy-face (if promela-xemacsp 'modeline 'region) + 'promela-fl-send-poll-face) + +;; some emacs-en don't define or have regexp-opt available. +(unless (functionp 'regexp-opt) + (defmacro regexp-opt (strings) + "Cheap imitation of `regexp-opt' since it's not availble in this emacs" + `(mapconcat 'identity ,strings "\\|"))) + + +;; ------------------------------------------------------------------------- +;; promela-mode font lock specifications/regular-expressions +;; - for help, look at definition of variable 'font-lock-keywords +;; - some fontification ideas from -- [engstrom:20010309.1435CST] +;; Pat Tullman (tullmann@cs.utah.edu) and +;; Ny Aina Razermera Mamy (ainarazr@cs.uoregon.edu) +;; both had promela-mode's that I discovered after starting this one... +;; (but neither did any sort of indentation ;-) + +(defconst promela-font-lock-keywords-1 nil + "Subdued level highlighting for Promela mode.") + +(defconst promela-font-lock-keywords-2 nil + "Medium level highlighting for Promela mode.") + +(defconst promela-font-lock-keywords-3 nil + "Gaudy level highlighting for Promela mode.") + +;; set each of those three variables now.. +(let ((promela-keywords + (eval-when-compile + (regexp-opt + '("active" "assert" "atomic" "break" "d_step" + "do" "dproctype" "else" "empty" "enabled" + "eval" "fi" "full" "goto" "hidden" "if" "init" + "inline" "len" "local" "mtype" "nempty" "never" + "nfull" "od" "of" "pcvalue" "printf" "priority" + "proctype" "provided" "run" "show" "skip" + "timeout" "trace" "typedef" "unless" "xr" "xs")))) + (promela-types + (eval-when-compile + (regexp-opt '("bit" "bool" "byte" "short" + "int" "unsigned" "chan"))))) + + ;; really simple fontification (strings and comments come for "free") + (setq promela-font-lock-keywords-1 + (list + ;; Keywords: + (cons (concat "\\<\\(" promela-keywords "\\)\\>") + 'font-lock-keyword-face) + ;; Types: + (cons (concat "\\<\\(" promela-types "\\)\\>") + 'font-lock-type-face) + ;; Special constants: + '("\\<_\\(np\\|pid\\|last\\)\\>" . font-lock-reference-face))) + + ;; more complex fontification + ;; add function (proctype) names, lables and goto statements + ;; also add send/receive/poll fontification + (setq promela-font-lock-keywords-2 + (append promela-font-lock-keywords-1 + (list + ;; ANY Pre-Processor directive (lazy method: any line beginning with "#[a-z]+") + '("^\\(#[ \t]*[a-z]+\\)" 1 'font-lock-preprocessor-face t) + + ;; "Functions" (proctype-s and inline-s) + (list (concat "\\<\\(" + (eval-when-compile + (regexp-opt '("run" "dproctype" "proctype" "inline"))) + "\\)\\>[ \t]*\\(\\sw+\\)?") + ;;'(1 'font-lock-keyword-face nil) + '(2 'font-lock-function-name-face nil t)) + + ;; Labels and GOTO labels + '("^\\(\\sw+\\):" 1 'font-lock-reference-face) + '("\\<\\(goto\\)\\>[ \t]+\\(\\sw+\\)" + ;;(1 'font-lock-keyword-face nil) + (2 'font-lock-reference-face nil t)) + + ;; Send, Receive and Poll + '("\\(\\sw+\\)\\(\\[[^\\?!]+\\]\\)?\\(\\??\\?\\|!?!\\)\\(\\sw+\\)" + (1 'font-lock-variable-name-face nil t) + (3 'promela-fl-send-poll-face nil t) + (4 'font-lock-reference-face nil t) + ) + ))) + + ;; most complex fontification + ;; add pre-processor directives, typed variables and hidden/typedef decls. + (setq promela-font-lock-keywords-3 + (append promela-font-lock-keywords-2 + (list + ;; ANY Pre-Processor directive (lazy method: any line beginning with "#[a-z]+") + ;;'("^\\(#[ \t]*[a-z]+\\)" 1 'font-lock-preprocessor-face t) + ;; "defined" in an #if or #elif and associated macro names + '("^#[ \t]*\\(el\\)?if\\>" + ("\\<\\(defined\\)\\>[ \t]*(?\\(\\sw+\\)" nil nil + (1 'font-lock-preprocessor-face nil t) + (2 'font-lock-reference-face nil t))) + '("^#[ \t]*ifn?def\\>" + ("[ \t]*\\(\\sw+\\)" nil nil + (1 'font-lock-reference-face nil t))) + ;; Filenames in #include <...> directives + '("^#[ \t]*include[ \t]+<\\([^>\"\n]+\\)>" 1 'font-lock-string-face nil t) + ;; Defined functions and constants/types (non-functions) + '("^#[ \t]*define[ \t]+" + ("\\(\\sw+\\)(" nil nil (1 'font-lock-function-name-face nil t)) + ("\\(\\sw+\\)[ \t]+\\(\\sw+\\)" nil nil (1 'font-lock-variable-name-face) + (2 'font-lock-reference-face nil t)) + ("\\(\\sw+\\)[^(]?" nil nil (1 'font-lock-reference-face nil t))) + + ;; Types AND variables + ;; - room for improvement: (i.e. don't currently): + ;; highlight user-defined types and asociated variable declarations + (list (concat "\\<\\(" promela-types "\\)\\>") + ;;'(1 'font-lock-type-face) + ;; now match the variables after the type definition, if any + '(promela-match-variable-or-declaration + nil nil + (1 'font-lock-variable-name-face) ;; nil t) + (2 font-lock-reference-face nil t))) + + ;; Typedef/hidden types and declarations + '("\\<\\(typedef\\|hidden\\)\\>[ \t]*\\(\\sw+\\)?" + ;;(1 'font-lock-keyword-face nil) + (2 'font-lock-type-face nil t) + ;; now match the variables after the type definition, if any + (promela-match-variable-or-declaration + nil nil + (1 'font-lock-variable-name-face nil t) + (2 'font-lock-reference-face nil t))) + ))) + ) + +(defvar promela-font-lock-keywords promela-font-lock-keywords-1 + "Default expressions to highlight in Promela mode.") + +;; Font-lock matcher functions: +(defun promela-match-variable-or-declaration (limit) + "Match, and move over, any declaration/definition item after point. +Matches after point, but ignores leading whitespace characters. +Does not move further than LIMIT. + +The expected syntax of a declaration/definition item is `word' (preceded +by optional whitespace) optionally followed by a `= value' (preceded and +followed by more optional whitespace) + +Thus the regexp matches after point: word [ = value ] + ^^^^ ^^^^^ +Where the match subexpressions are: 1 2 + +The item is delimited by (match-beginning 1) and (match-end 1). +If (match-beginning 2) is non-nil, the item is followed by a `value'." + (when (looking-at "[ \t]*\\(\\sw+\\)[ \t]*=?[ \t]*\\(\\sw+\\)?[ \t]*,?") + (goto-char (min limit (match-end 0))))) + + +;; ------------------------------------------------------------------------- +;; "install" promela-mode font lock specifications + +;; FMI: look up 'font-lock-defaults +(defconst promela-font-lock-defaults + '( + (promela-font-lock-keywords + promela-font-lock-keywords-1 + promela-font-lock-keywords-2 + promela-font-lock-keywords-3) ;; font-lock stuff (keywords) + nil ;; keywords-only flag + nil ;; case-fold keyword searching + ;;((?_ . "w") (?$ . ".")) ;; mods to syntax table + nil ;; mods to syntax table (see below) + nil ;; syntax-begin + (font-lock-mark-block-function . mark-defun)) +) + +;; "install" the font-lock-defaults based upon version of emacs we have +(cond (promela-xemacsp + (put 'promela-mode 'font-lock-defaults promela-font-lock-defaults)) + ((not (assq 'promela-mode font-lock-defaults-alist)) + (setq font-lock-defaults-alist + (cons + (cons 'promela-mode promela-font-lock-defaults) + font-lock-defaults-alist)))) + + +;; ------------------------------------------------------------------------- +;; other promela-mode specific definitions + +(defconst promela-defun-prompt-regexp + "^[ \t]*\\(d?proctype\\|init\\|inline\\|never\\|trace\\|typedef\\|mtype\\s-+=\\)[^{]*" + "Regexp describing the beginning of a Promela top-level definition.") + +(defvar promela-mode-syntax-table nil + "Syntax table in use in PROMELA-mode buffers.") +(if promela-mode-syntax-table + () + (setq promela-mode-syntax-table (make-syntax-table)) + (modify-syntax-entry ?\\ "\\" promela-mode-syntax-table) + (modify-syntax-entry ?/ ". 14" promela-mode-syntax-table) + (modify-syntax-entry ?* ". 23" promela-mode-syntax-table) + (modify-syntax-entry ?+ "." promela-mode-syntax-table) + (modify-syntax-entry ?- "." promela-mode-syntax-table) + (modify-syntax-entry ?= "." promela-mode-syntax-table) + (modify-syntax-entry ?% "." promela-mode-syntax-table) + (modify-syntax-entry ?< "." promela-mode-syntax-table) + (modify-syntax-entry ?> "." promela-mode-syntax-table) + (modify-syntax-entry ?& "." promela-mode-syntax-table) + (modify-syntax-entry ?| "." promela-mode-syntax-table) + (modify-syntax-entry ?. "_" promela-mode-syntax-table) + (modify-syntax-entry ?_ "w" promela-mode-syntax-table) + (modify-syntax-entry ?\' "\"" promela-mode-syntax-table) + ) + +(defvar promela-mode-abbrev-table nil + "*Abbrev table in use in promela-mode buffers.") +(if promela-mode-abbrev-table + nil + (define-abbrev-table 'promela-mode-abbrev-table + '( +;; Commented out for now - need to think about what abbrevs make sense +;; ("assert" "ASSERT" promela-check-expansion 0) +;; ("d_step" "D_STEP" promela-check-expansion 0) +;; ("break" "BREAK" promela-check-expansion 0) +;; ("do" "DO" promela-check-expansion 0) +;; ("proctype" "PROCTYPE" promela-check-expansion 0) + ))) + +(defvar promela-mode-map nil + "Keymap for promela-mode.") +(if promela-mode-map + nil + (setq promela-mode-map (make-sparse-keymap)) + (define-key promela-mode-map "\t" 'promela-indent-command) + (define-key promela-mode-map "\C-m" 'promela-newline-and-indent) + ;(define-key promela-mode-map 'backspace 'backward-delete-char-untabify) + (define-key promela-mode-map "\C-c\C-p" 'promela-beginning-of-block) + ;(define-key promela-mode-map "\C-c\C-n" 'promela-end-of-block) + (define-key promela-mode-map "\M-\C-a" 'promela-beginning-of-defun) + ;(define-key promela-mode-map "\M-\C-e" 'promela-end-of-defun) + (define-key promela-mode-map "\C-c(" 'promela-toggle-auto-match-delimiter) + (define-key promela-mode-map "{" 'promela-open-delimiter) + (define-key promela-mode-map "}" 'promela-close-delimiter) + (define-key promela-mode-map "(" 'promela-open-delimiter) + (define-key promela-mode-map ")" 'promela-close-delimiter) + (define-key promela-mode-map "[" 'promela-open-delimiter) + (define-key promela-mode-map "]" 'promela-close-delimiter) + (define-key promela-mode-map ";" 'promela-insert-and-indent) + (define-key promela-mode-map ":" 'promela-insert-and-indent) + ;; + ;; this is preliminary at best - use at your own risk: + (define-key promela-mode-map "\C-c\C-s" 'promela-syntax-check) + ;; + ;;(define-key promela-mode-map "\C-c\C-d" 'promela-mode-toggle-debug) + ;;(define-key promela-mode-map "\C-c\C-r" 'promela-mode-revert-buffer) + ) + +(defvar promela-matching-delimiter-alist + '( (?( . ?)) + (?[ . ?]) + (?{ . "\n}") + ;(?< . ?>) + (?\' . ?\') + (?\` . ?\`) + (?\" . ?\") ) + "List of pairs of matching open/close delimiters - for auto-insert") + + +;; ------------------------------------------------------------------------- +;; Promela-mode itself + +(defun promela-mode () + "Major mode for editing PROMELA code. +\\{promela-mode-map} + +Variables controlling indentation style: + promela-block-indent + Relative offset of lines within a block (`{') construct. + + promela-selection-indent + Relative offset of option lines within a selection (`if') + or iteration (`do') construct. + + promela-selection-option-indent + Relative offset of lines after/within options (`::') within + selection or iteration constructs. + + promela-comment-col + Defines the desired comment column for comments to the right of text. + + promela-tab-always-indent + Non-nil means TAB in PROMELA mode should always reindent the current + line, regardless of where in the line the point is when the TAB + command is used. + + promela-auto-match-delimiter + Non-nil means typing an open-delimiter (i.e. parentheses, brace, + quote, etc) should also insert the matching closing delmiter + character. + +Turning on PROMELA mode calls the value of the variable promela-mode-hook with +no args, if that value is non-nil. + +For example: ' + (setq promela-mode-hook '(lambda () + (setq promela-block-indent 2) + (setq promela-selection-indent 0) + (setq promela-selection-option-indent 2) + (local-set-key \"\\C-m\" 'promela-indent-newline-indent) + ))' + +will indent block two steps, will make selection options aligned with DO/IF +and sub-option lines indent to a column after the `::'. Also, lines will +be reindented when you hit RETURN. + +Note that promela-mode adhears to the font-lock \"standards\" and +defines several \"levels\" of fontification or colorization. The +default is fairly gaudy, so if you would prefer a bit less, please see +the documentation for the variable: `font-lock-maximum-decoration'. +" + (interactive) + (kill-all-local-variables) + (setq mode-name "Promela") + (setq major-mode 'promela-mode) + (use-local-map promela-mode-map) + (set-syntax-table promela-mode-syntax-table) + (setq local-abbrev-table promela-mode-abbrev-table) + + ;; Make local variables + (make-local-variable 'case-fold-search) + (make-local-variable 'paragraph-start) + (make-local-variable 'paragraph-separate) + (make-local-variable 'paragraph-ignore-fill-prefix) + (make-local-variable 'indent-line-function) + (make-local-variable 'indent-region-function) + (make-local-variable 'parse-sexp-ignore-comments) + (make-local-variable 'comment-start) + (make-local-variable 'comment-end) + (make-local-variable 'comment-column) + (make-local-variable 'comment-start-skip) + (make-local-variable 'comment-indent-hook) + (make-local-variable 'defun-prompt-regexp) + (make-local-variable 'compile-command) + ;; Now set their values + (setq case-fold-search t + paragraph-start (concat "^$\\|" page-delimiter) + paragraph-separate paragraph-start + paragraph-ignore-fill-prefix t + indent-line-function 'promela-indent-command + ;;indent-region-function 'promela-indent-region + parse-sexp-ignore-comments t + comment-start "/* " + comment-end " */" + comment-column 32 + comment-start-skip "/\\*+ *" + ;;comment-start-skip "/\\*+ *\\|// *" + ;;comment-indent-hook 'promela-comment-indent + defun-prompt-regexp promela-defun-prompt-regexp + ) + + ;; Turn on font-lock mode + ;; (and promela-font-lock-mode (font-lock-mode)) + (font-lock-mode) + + ;; Finally, run the hooks and be done. + (run-hooks 'promela-mode-hook)) + + +;; ------------------------------------------------------------------------- +;; Interactive functions +;; + +(defun promela-mode-version () + "Print the current version of promela-mode in the minibuffer" + (interactive) + (message (concat "Promela-Mode: " promela-mode-version))) + +(defun promela-beginning-of-block () + "Move backward to start of containing block. +Containing block may be `{', `do' or `if' construct, or comment." + (interactive) + (goto-char (promela-find-start-of-containing-block-or-comment))) + +(defun promela-beginning-of-defun (&optional arg) + "Move backward to the beginning of a defun. +With argument, do it that many times. +Negative arg -N means move forward to Nth following beginning of defun. +Returns t unless search stops due to beginning or end of buffer. + +See also 'beginning-of-defun. + +This is a Promela-mode specific version since default (in xemacs 19.16 and +NT-Emacs 20) don't seem to skip comments - they will stop inside them. + +Also, this makes sure that the beginning of the defun is actually the +line which starts the proctype/init/etc., not just the open-brace." + (interactive "p") + (beginning-of-defun arg) + (if (not (looking-at promela-defun-prompt-regexp)) + (re-search-backward promela-defun-prompt-regexp nil t)) + (if (promela-inside-comment-p) + (goto-char (promela-find-start-of-containing-comment)))) + +(defun promela-indent-command () + "Indent the current line as PROMELA code." + (interactive) + (if (and (not promela-tab-always-indent) + (save-excursion + (skip-chars-backward " \t") + (not (bolp)))) + (tab-to-tab-stop) + (promela-indent-line))) + +(defun promela-newline-and-indent () + "Promela-mode specific newline-and-indent which expands abbrevs before +running a regular newline-and-indent." + (interactive) + (if abbrev-mode + (expand-abbrev)) + (newline-and-indent)) + +(defun promela-indent-newline-indent () + "Promela-mode specific newline-and-indent which expands abbrevs and +indents the current line before running a regular newline-and-indent." + (interactive) + (save-excursion (promela-indent-command)) + (if abbrev-mode + (expand-abbrev)) + (newline-and-indent)) + +(defun promela-insert-and-indent () + "Insert the last character typed and re-indent the current line" + (interactive) + (insert last-command-char) + (save-excursion (promela-indent-command))) + +(defun promela-open-delimiter () + "Inserts the open and matching close delimiters, indenting as appropriate." + (interactive) + (insert last-command-char) + (if (and promela-auto-match-delimiter (not (promela-inside-comment-p))) + (save-excursion + (insert (cdr (assq last-command-char promela-matching-delimiter-alist))) + (promela-indent-command)))) + +(defun promela-close-delimiter () + "Inserts and indents a close delimiter." + (interactive) + (insert last-command-char) + (if (not (promela-inside-comment-p)) + (save-excursion (promela-indent-command)))) + +(defun promela-toggle-auto-match-delimiter () + "Toggle auto-insertion of parens and other delimiters. +See variable `promela-auto-insert-matching-delimiter'" + (interactive) + (setq promela-auto-match-delimiter + (not promela-auto-match-delimiter)) + (message (concat "Promela auto-insert matching delimiters " + (if promela-auto-match-delimiter + "enabled" "disabled")))) + + +;; ------------------------------------------------------------------------- +;; Compilation/Verification functions + +;; all of this is in serious "beta" mode - don't trust it ;-) +(setq + promela-compile-command "spin " + promela-syntax-check-args "-a -v " +) + +;;(setq compilation-error-regexp-alist +;; (append compilation-error-regexp-alist +;; '(("spin: +line +\\([0-9]+\\) +\"\\([^\"]+\\)\"" 2 1)))) + +(defun promela-syntax-check () + (interactive) + (compile (concat promela-compile-command + promela-syntax-check-args + (buffer-name)))) + + +;; ------------------------------------------------------------------------- +;; Indentation support functions + +(defun promela-indent-around-label () + "Indent the current line as PROMELA code, +but make sure to consider the label at the beginning of the line." + (beginning-of-line) + (delete-horizontal-space) ; delete any leading whitespace + (if (not (looking-at "\\sw+:\\([ \t]*\\)")) + (error "promela-indent-around-label: no label on this line") + (goto-char (match-beginning 1)) + (let* ((space (length (match-string 1))) + (indent (promela-calc-indent)) + (wanted (max 0 (- indent (current-column))))) + (if (>= space wanted) + (delete-region (point) (+ (point) (- space wanted))) + (goto-char (+ (point) space)) + (indent-to-column indent))))) + +;; Note that indentation is based ENTIRELY upon the indentation of the +;; previous line(s), esp. the previous non-blank line and the line +;; starting the current containgng block... +(defun promela-indent-line () + "Indent the current line as PROMELA code. +Return the amount the by which the indentation changed." + (beginning-of-line) + (if (looking-at "[ \t]*\\sw+:") + (promela-indent-around-label) + (let ((indent (promela-calc-indent)) + beg + shift-amt + (pos (- (point-max) (point)))) + (setq beg (point)) + (skip-chars-forward " \t") + (setq shift-amt (- indent (current-column))) + (if (zerop shift-amt) + (if (> (- (point-max) pos) (point)) + (goto-char (- (point-max) pos))) + (delete-region beg (point)) + (indent-to indent) + (if (> (- (point-max) pos) (point)) + (goto-char (- (point-max) pos)))) + shift-amt))) + +(defun promela-calc-indent () + "Return the appropriate indentation for this line as an int." + (save-excursion + (beginning-of-line) + (let* ((orig-point (point)) + (state (promela-parse-partial-sexp)) + (paren-depth (nth 0 state)) + (paren-point (or (nth 1 state) 1)) + (paren-char (char-after paren-point))) + ;;(what-cursor-position) + (cond + ;; Indent not-at-all - inside a string + ((nth 3 state) + (current-indentation)) + ;; Indent inside a comment + ((nth 4 state) + (promela-calc-indent-within-comment)) + ;; looking at a pre-processor directive - indent=0 + ((looking-at "[ \t]*#\\(define\\|if\\(n?def\\)?\\|else\\|endif\\)") + 0) + ;; If we're not inside a "true" block (i.e. "{}"), then indent=0 + ;; I think this is fair, since no (indentable) code in promela + ;; exists outside of a proctype or similar "{ .. }" structure. + ((zerop paren-depth) + 0) + ;; Indent relative to non curly-brace "paren" + ;; [ NOTE: I'm saving this, but don't use it any more. + ;; Now, we let parens be indented like curly braces + ;;((and (>= paren-depth 1) (not (char-equal ?\{ paren-char))) + ;; (goto-char paren-point) + ;; (1+ (current-column))) + ;; + ;; Last option: indent relative to contaning block(s) + (t + (goto-char orig-point) + (promela-calc-indent-within-block paren-point)))))) + +(defun promela-calc-indent-within-block (&optional limit) + "Return the appropriate indentation for this line, assume within block. +with optional arg, limit search back to `limit'" + (save-excursion + (let* ((stop (or limit 1)) + (block-point (promela-find-start-of-containing-block stop)) + (block-type (promela-block-type-after block-point)) + (indent-point (point)) + (indent-type (promela-block-type-after indent-point))) + (if (not block-type) 0 + ;;(message "paren: %d (%d); block: %s (%d); indent: %s (%d); stop: %d" + ;; paren-depth paren-point block-type block-point + ;; indent-type indent-point stop) + (goto-char block-point) + (cond + ;; Indent (options) inside "if" or "do" + ((memq block-type '(selection iteration)) + (if (re-search-forward "\\(do\\|if\\)[ \t]*::" indent-point t) + (- (current-column) 2) + (+ (current-column) promela-selection-indent))) + ;; indent (generic code) inside "::" option + ((eq 'option block-type) + (if (and (not indent-type) + (re-search-forward "::.*->[ \t]*\\sw" + (save-excursion (end-of-line) (point)) + t)) + (1- (current-column)) + (+ (current-column) promela-selection-option-indent)) + ) + ;; indent code inside "{" + ((eq 'block block-type) + (cond + ;; if we are indenting the end of a block, + ;; use indentation of start-of-block + ((equal 'block-end indent-type) + (current-indentation)) + ;; if the start of the code inside the block is not at eol + ;; then indent to the same column as the block start +some + ;; [ but ignore comments after "{" ] + ((and (not (promela-effective-eolp (1+ (point)))) + (not (looking-at "{[ \t]*/\\*"))) + (forward-char) ; skip block-start + (skip-chars-forward "{ \t") ; skip whitespace, if any + (current-column)) + ;; anything else we indent +promela-block-indent from + ;; the indentation of the start of block (where we are now) + (t + (+ (current-indentation) + promela-block-indent)))) + ;; dunno what kind of block this is - sound an error + (t + (error "promela-calc-indent-within-block: unknown block type: %s" block-type) + (current-indentation))))))) + +(defun promela-calc-indent-within-comment () + "Return the indentation amount for line, assuming that the +current line is to be regarded as part of a block comment." + (save-excursion + (beginning-of-line) + (skip-chars-forward " \t") + (let ((indenting-end-of-comment (looking-at "\\*/")) + (indenting-blank-line (eolp))) + ;; if line is NOT blank and next char is NOT a "*' + (if (not (or indenting-blank-line (= (following-char) ?\*))) + ;; leave indent alone + (current-column) + ;; otherwise look back for _PREVIOUS_ possible nested comment start + (let ((comment-start (save-excursion + (re-search-backward comment-start-skip)))) + ;; and see if there is an appropriate middle-comment "*" + (if (re-search-backward "^[ \t]+\\*" comment-start t) + (current-indentation) + ;; guess not, so indent relative to comment start + (goto-char comment-start) + (if indenting-end-of-comment + (current-column) + (1+ (current-column))))))))) + + +;; ------------------------------------------------------------------------- +;; Misc other support functions + +(defun promela-parse-partial-sexp (&optional start limit) + "Return the partial parse state of current defun or from optional start +to end limit" + (save-excursion + (let ((end (or limit (point)))) + (if start + (goto-char start) + (promela-beginning-of-defun)) + (parse-partial-sexp (point) end)))) + +;;(defun promela-at-end-of-block-p () +;; "Return t if cursor is at the end of a promela block" +;; (save-excursion +;; (let ((eol (progn (end-of-line) (point)))) +;; (beginning-of-line) +;; (skip-chars-forward " \t") +;; ;;(re-search-forward "\\(}\\|\\b\\(od\\|fi\\)\\b\\)" eol t)))) +;; (looking-at "[ \t]*\\(od\\|fi\\)\\b")))) + +(defun promela-inside-comment-p () + "Check if the point is inside a comment block." + (save-excursion + (let ((origpoint (point)) + state) + (goto-char 1) + (while (> origpoint (point)) + (setq state (parse-partial-sexp (point) origpoint 0))) + (nth 4 state)))) + +(defun promela-inside-comment-or-string-p () + "Check if the point is inside a comment or a string." + (save-excursion + (let ((origpoint (point)) + state) + (goto-char 1) + (while (> origpoint (point)) + (setq state (parse-partial-sexp (point) origpoint 0))) + (or (nth 3 state) (nth 4 state))))) + + +(defun promela-effective-eolp (&optional point) + "Check if we are at the effective end-of-line, ignoring whitespace" + (save-excursion + (if point (goto-char point)) + (skip-chars-forward " \t") + (eolp))) + +(defun promela-check-expansion () + "If abbrev was made within a comment or a string, de-abbrev!" + (if promela-inside-comment-or-string-p + (unexpand-abbrev))) + +(defun promela-block-type-after (&optional point) + "Return the type of block after current point or parameter as a symbol. +Return one of 'iteration `do', 'selection `if', 'option `::', +'block `{' or `}' or nil if none of the above match." + (save-excursion + (goto-char (or point (point))) + (skip-chars-forward " \t") + (cond + ((looking-at "do\\b") 'iteration) + ;;((looking-at "od\\b") 'iteration-end) + ((looking-at "if\\b") 'selection) + ;;((looking-at "fi\\b") 'selection-end) + ((looking-at "::") 'option) + ((looking-at "[{(]") 'block) + ((looking-at "[})]") 'block-end) + (t nil)))) + +(defun promela-find-start-of-containing-comment (&optional limit) + "Return the start point of the containing comment block. +Stop at `limit' or beginning of buffer." + (let ((stop (or limit 1))) + (save-excursion + (while (and (>= (point) stop) + (nth 4 (promela-parse-partial-sexp))) + (re-search-backward comment-start-skip stop t)) + (point)))) + +(defun promela-find-start-of-containing-block (&optional limit) + "Return the start point of the containing `do', `if', `::' or +`{' block or containing comment. +Stop at `limit' or beginning of buffer." + (save-excursion + (skip-chars-forward " \t") + (let* ((type (promela-block-type-after)) + (stop (or limit + (save-excursion (promela-beginning-of-defun) (point)))) + (state (promela-parse-partial-sexp stop)) + (level (if (looking-at "\\(od\\|fi\\)\\b") + 2 + (if (zerop (nth 0 state)) 0 1)))) + ;;(message "find-start-of-containing-block: type: %s; level %d; stop %d" + ;; type level stop) + (while (and (> (point) stop) (not (zerop level))) + (re-search-backward + "\\({\\|}\\|::\\|\\b\\(do\\|od\\|if\\|fi\\)\\b\\)" + stop 'move) + ;;(message "looking from %d back-to %d" (point) stop) + (setq state (promela-parse-partial-sexp stop)) + (setq level (+ level + (cond ((or (nth 3 state) (nth 4 state)) 0) + ((and (= 1 level) (looking-at "::") + (not (equal type 'option))) -1) + ((looking-at "\\({\\|\\(do\\|if\\)\\b\\)") -1) + ((looking-at "\\(}\\|\\(od\\|fi\\)\\b\\)") +1) + (t 0))))) + (point)))) + +(defun promela-find-start-of-containing-block-or-comment (&optional limit) + "Return the start point of the containing comment or +the start of the containing `do', `if', `::' or `{' block. +Stop at limit or beginning of buffer." + (if (promela-inside-comment-p) + (promela-find-start-of-containing-comment limit) + (promela-find-start-of-containing-block limit))) + +;; ------------------------------------------------------------------------- +;; Debugging/testing + +;; (defun promela-mode-toggle-debug () +;; (interactive) +;; (make-local-variable 'debug-on-error) +;; (setq debug-on-error (not debug-on-error))) + +;;(defun promela-mode-revert-buffer () +;; (interactive) +;; (revert-buffer t t)) + +;; ------------------------------------------------------------------------- +;;###autoload + +(provide 'promela-mode) + + +;;---------------------------------------------------------------------- +;; Change History: +;; +;; $Log: promela-mode.el,v $ +;; Revision 1.11 2001/07/09 18:36:45 engstrom +;; - added comments on use of font-lock-maximum-decoration +;; - moved basic preprocess directive fontification to "level 2" +;; +;; Revision 1.10 2001/05/22 16:29:59 engstrom +;; - fixed error introduced in fontification levels stuff (xemacs only) +;; +;; Revision 1.9 2001/05/22 16:21:29 engstrom +;; - commented out the compilation / syntax check stuff for now +;; +;; Revision 1.8 2001/05/22 16:18:49 engstrom +;; - Munched history in preparation for first non-Honeywell release +;; - Added "levels" of fontification to be controlled by the std. variable: +;; 'font-lock-maximum-decoration' +;; +;; Revision 1.7 2001/04/20 01:41:46 engstrom +;; Revision 1.6 2001/04/06 23:57:18 engstrom +;; Revision 1.5 2001/04/04 20:04:15 engstrom +;; Revision 1.4 2001/03/15 02:22:18 engstrom +;; Revision 1.3 2001/03/09 19:39:51 engstrom +;; Revision 1.2 2001/03/01 18:07:47 engstrom +;; Revision 1.1 2001/02/01 xx:xx:xx engstrom +;; migrated to CVS versioning... +;; Pre-CVS-History: +;; 99-10-04 V0.4 EDE Fixed bug in end-of-block indentation +;; Simplified indentation code significantly +;; 99-09-2x V0.3 EDE Hacked on indentation more while at FM'99 +;; 99-09-16 V0.2 EDE Hacked, hacked, hacked on indentation +;; 99-04-01 V0.1 EDE Introduced (less-than) half-baked indentation +;; 98-11-05 V0.0 EDE Created - much code stolen from rexx-mode.el +;; Mostly just a fontification mode - +;; (indentation is HARD ;-) +;; +;; EOF promela-mode.el -- cgit v1.2.3