From cfc2d407d797b4fb1a4c80b13463e947d3588e2a Mon Sep 17 00:00:00 2001 From: Jan Steemann Date: Sat, 10 May 2014 00:43:14 +0200 Subject: [PATCH] added tests for task management --- CHANGELOG | 15 +- UnitTests/Makefile.unittests | 1 + arangod/V8Server/V8PeriodicJob.cpp | 58 +- arangod/V8Server/V8PeriodicJob.h | 25 +- arangod/V8Server/V8PeriodicTask.cpp | 37 +- arangod/V8Server/V8PeriodicTask.h | 29 +- arangod/V8Server/v8-actions.cpp | 104 +- .../frontend/js/bootstrap/module-internal.js | 7 + js/common/bootstrap/module-internal.js | 7 + js/common/modules/jslint/jslint.new.js | 4277 +++++++++++++++++ js/common/modules/org/arangodb/statistics.js | 4 +- js/server/server.js | 4 +- js/server/tests/shell-tasks.js | 433 ++ lib/Scheduler/Scheduler.cpp | 9 +- lib/Scheduler/Task.cpp | 39 +- lib/Scheduler/Task.h | 10 +- 16 files changed, 4923 insertions(+), 136 deletions(-) create mode 100644 js/common/modules/jslint/jslint.new.js create mode 100644 js/server/tests/shell-tasks.js diff --git a/CHANGELOG b/CHANGELOG index f91679b8b5..06f68fb97d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,13 +1,19 @@ v2.1.0 (XXXX-XX-XX) ------------------- +* added server-side periodic task management functions: + + - internal.executeTask(): executes a periodic task + - internal.deleteTask(): removes a periodic task + - internal.getTasks(): lists all periodic tasks + + the previous undocumented function `internal.definePeriodic` is now + deprecated and will be removed in a future release. + * decrease the size of some seldomly used system collections on creation. This will make these collections use less disk space and mapped memory. -* fixed issue #848: db.someEdgeCollection.inEdge does not return correct - value when called the 2nd time after a .save to the edge collection - * added AQL date functions * added AQL FLATTEN() and FLATTEN_RECURSIVE() list functions @@ -127,6 +133,9 @@ v2.0.8 (XXXX-XX-XX) watch API crashed it. This was because atomic operations worked on data structures that were not properly aligned on 32 bit systems. +* fixed issue #848: db.someEdgeCollection.inEdge does not return correct + value when called the 2nd time after a .save to the edge collection + v2.0.7 (2014-05-05) ------------------- diff --git a/UnitTests/Makefile.unittests b/UnitTests/Makefile.unittests index 14e5f46316..b31d106ff7 100755 --- a/UnitTests/Makefile.unittests +++ b/UnitTests/Makefile.unittests @@ -370,6 +370,7 @@ SHELL_COMMON = \ SHELL_SERVER_ONLY = \ @top_srcdir@/js/server/tests/shell-sharding-helpers.js \ @top_srcdir@/js/server/tests/shell-compaction-noncluster.js \ + @top_srcdir@/js/server/tests/shell-tasks.js \ @top_srcdir@/js/server/tests/shell-transactions-noncluster.js \ @top_srcdir@/js/server/tests/shell-routing.js \ @top_srcdir@/js/server/tests/shell-any-noncluster.js \ diff --git a/arangod/V8Server/V8PeriodicJob.cpp b/arangod/V8Server/V8PeriodicJob.cpp index 9f21f42c57..819c2a67dd 100644 --- a/arangod/V8Server/V8PeriodicJob.cpp +++ b/arangod/V8Server/V8PeriodicJob.cpp @@ -28,7 +28,9 @@ #include "V8PeriodicJob.h" #include "Basics/StringUtils.h" +#include "BasicsC/json.h" #include "BasicsC/logging.h" +#include "V8/v8-conv.h" #include "V8/v8-utils.h" #include "V8Server/ApplicationV8.h" #include "VocBase/vocbase.h" @@ -48,15 +50,13 @@ using namespace triagens::arango; V8PeriodicJob::V8PeriodicJob (TRI_vocbase_t* vocbase, ApplicationV8* v8Dealer, - string const& module, - string const& func, - string const& parameter) + string const& command, + TRI_json_t const* parameters) : Job("V8 Periodic Job"), _vocbase(vocbase), _v8Dealer(v8Dealer), - _module(module), - _func(func), - _parameter(parameter), + _command(command), + _parameters(parameters), _canceled(0) { } @@ -101,17 +101,43 @@ Job::status_t V8PeriodicJob::work () { // now execute the function within this context { v8::HandleScope scope; + + // get built-in Function constructor (see ECMA-262 5th edition 15.3.2) + v8::Handle current = v8::Context::GetCurrent()->Global(); + v8::Local ctor = v8::Local::Cast(current->Get(v8::String::New("Function"))); + + // Invoke Function constructor to create function with the given body and no arguments + v8::Handle args[2] = { v8::String::New("params"), v8::String::New(_command.c_str(), _command.size()) }; + v8::Local function = ctor->NewInstance(2, args); - string module = StringUtils::escape(_module, "\""); - string func = StringUtils::escape(_func, "\""); - string parameter = StringUtils::escape(_parameter, "\""); - - string command = "(require(\"" + module + "\")[\"" + func + "\"])(\"" + parameter + "\")"; - - TRI_ExecuteJavaScriptString(context->_context, - v8::String::New(command.c_str(), (int) command.size()), - v8::String::New("periodic function"), - true); + v8::Handle action = v8::Local::Cast(function); + + if (action.IsEmpty()) { + _v8Dealer->exitContext(context); + // TODO: adjust exit code?? + return status_t(JOB_DONE); + } + + v8::Handle fArgs; + if (_parameters != 0) { + fArgs = TRI_ObjectJson(_parameters); + } + else { + fArgs = v8::Undefined(); + } + + // call the function + v8::TryCatch tryCatch; + action->Call(current, 1, &fArgs); + + if (tryCatch.HasCaught()) { + if (tryCatch.CanContinue()) { + TRI_LogV8Exception(&tryCatch); + } + else { + LOG_WARNING("caught non-printable exception in periodic task"); + } + } } _v8Dealer->exitContext(context); diff --git a/arangod/V8Server/V8PeriodicJob.h b/arangod/V8Server/V8PeriodicJob.h index 1162b6fc7e..eba8be51d4 100644 --- a/arangod/V8Server/V8PeriodicJob.h +++ b/arangod/V8Server/V8PeriodicJob.h @@ -31,6 +31,10 @@ #include "Dispatcher/Job.h" #include "VocBase/vocbase.h" +extern "C" { + struct TRI_json_s; +} + // ----------------------------------------------------------------------------- // --SECTION-- class V8PeriodicJob // ----------------------------------------------------------------------------- @@ -56,9 +60,8 @@ namespace triagens { V8PeriodicJob (TRI_vocbase_t*, ApplicationV8*, - string const& module, - string const& func, - string const& parameter); + std::string const&, + struct TRI_json_s const*); // ----------------------------------------------------------------------------- // --SECTION-- Job methods @@ -76,7 +79,7 @@ namespace triagens { /// {@inheritDoc} //////////////////////////////////////////////////////////////////////////////// - const string& queue (); + const std::string& queue (); //////////////////////////////////////////////////////////////////////////////// /// {@inheritDoc} @@ -127,22 +130,16 @@ namespace triagens { ApplicationV8* _v8Dealer; //////////////////////////////////////////////////////////////////////////////// -/// @brief module name +/// @brief the command to execute //////////////////////////////////////////////////////////////////////////////// - const std::string _module; + std::string const _command; //////////////////////////////////////////////////////////////////////////////// -/// @brief function name +/// @brief paramaters //////////////////////////////////////////////////////////////////////////////// - const std::string _func; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief paramater string -//////////////////////////////////////////////////////////////////////////////// - - const std::string _parameter; + struct TRI_json_s const* _parameters; //////////////////////////////////////////////////////////////////////////////// /// @brief cancel flag diff --git a/arangod/V8Server/V8PeriodicTask.cpp b/arangod/V8Server/V8PeriodicTask.cpp index 95b4e8e8ea..d19b810ddb 100644 --- a/arangod/V8Server/V8PeriodicTask.cpp +++ b/arangod/V8Server/V8PeriodicTask.cpp @@ -27,8 +27,10 @@ #include "V8PeriodicTask.h" +#include "BasicsC/json.h" #include "Dispatcher/Dispatcher.h" #include "Scheduler/Scheduler.h" +#include "V8/v8-conv.h" #include "V8Server/V8PeriodicJob.h" #include "VocBase/server.h" @@ -52,17 +54,15 @@ V8PeriodicTask::V8PeriodicTask (string const& id, Dispatcher* dispatcher, double offset, double period, - string const& module, - string const& func, - string const& parameter) + string const& command, + TRI_json_t* parameters) : Task(id, name), PeriodicTask(offset, period), _vocbase(vocbase), _v8Dealer(v8Dealer), _dispatcher(dispatcher), - _module(module), - _func(func), - _parameter(parameter) { + _command(command), + _parameters(parameters) { assert(vocbase != 0); @@ -77,12 +77,30 @@ V8PeriodicTask::V8PeriodicTask (string const& id, V8PeriodicTask::~V8PeriodicTask () { // decrease reference counter for the database used TRI_ReleaseVocBase(_vocbase); + + if (_parameters != 0) { + TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, _parameters); + } } // ----------------------------------------------------------------------------- // --SECTION-- PeriodicTask methods // ----------------------------------------------------------------------------- +//////////////////////////////////////////////////////////////////////////////// +/// @brief get a task specific description in JSON format +//////////////////////////////////////////////////////////////////////////////// + +void V8PeriodicTask::getDescription (TRI_json_t* json) { + PeriodicTask::getDescription(json); + + TRI_json_t* cmd = TRI_CreateString2CopyJson(TRI_UNKNOWN_MEM_ZONE, _command.c_str(), _command.size()); + TRI_Insert3ArrayJson(TRI_UNKNOWN_MEM_ZONE, json, "command", cmd); + + TRI_json_t* db = TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, _vocbase->_name); + TRI_Insert3ArrayJson(TRI_UNKNOWN_MEM_ZONE, json, "database", db); +} + //////////////////////////////////////////////////////////////////////////////// /// @brief handles the next tick //////////////////////////////////////////////////////////////////////////////// @@ -91,10 +109,9 @@ bool V8PeriodicTask::handlePeriod () { V8PeriodicJob* job = new V8PeriodicJob( _vocbase, _v8Dealer, - _module, - _func, - _parameter); - + "(function (params) { " + _command + " } )(params);", + _parameters); + _dispatcher->addJob(job); return true; diff --git a/arangod/V8Server/V8PeriodicTask.h b/arangod/V8Server/V8PeriodicTask.h index 51bc403c56..7813e8fcf6 100644 --- a/arangod/V8Server/V8PeriodicTask.h +++ b/arangod/V8Server/V8PeriodicTask.h @@ -32,6 +32,10 @@ #include "VocBase/vocbase.h" +extern "C" { + struct TRI_json_s; +} + // ----------------------------------------------------------------------------- // --SECTION-- class V8PeriodicTask // ----------------------------------------------------------------------------- @@ -65,9 +69,8 @@ namespace triagens { rest::Dispatcher*, double, double, - string const&, - string const&, - string const&); + std::string const&, + struct TRI_json_s*); //////////////////////////////////////////////////////////////////////////////// /// @brief destructor @@ -81,6 +84,12 @@ namespace triagens { protected: +//////////////////////////////////////////////////////////////////////////////// +/// @brief get a task specific description in JSON format +//////////////////////////////////////////////////////////////////////////////// + + virtual void getDescription (struct TRI_json_s*); + //////////////////////////////////////////////////////////////////////////////// /// @brief whether or not the task is user-defined //////////////////////////////////////////////////////////////////////////////// @@ -126,22 +135,16 @@ namespace triagens { rest::Dispatcher* _dispatcher; //////////////////////////////////////////////////////////////////////////////// -/// @brief module name +/// @brief command to execute //////////////////////////////////////////////////////////////////////////////// - std::string const _module; + std::string const _command; //////////////////////////////////////////////////////////////////////////////// -/// @brief function name +/// @brief paramaters //////////////////////////////////////////////////////////////////////////////// - std::string const _func; - -//////////////////////////////////////////////////////////////////////////////// -/// @brief paramater string -//////////////////////////////////////////////////////////////////////////////// - - std::string const _parameter; + struct TRI_json_s* _parameters; }; } diff --git a/arangod/V8Server/v8-actions.cpp b/arangod/V8Server/v8-actions.cpp index 86724512fd..94cf81d205 100644 --- a/arangod/V8Server/v8-actions.cpp +++ b/arangod/V8Server/v8-actions.cpp @@ -48,6 +48,7 @@ #include "V8Server/V8PeriodicTask.h" #include "V8Server/v8-vocbase.h" #include "VocBase/server.h" +#include "VocBase/vocbase.h" #ifdef TRI_ENABLE_CLUSTER @@ -75,12 +76,6 @@ static TRI_action_result_t ExecuteActionVocbase (TRI_vocbase_t* vocbase, // --SECTION-- private variables // ----------------------------------------------------------------------------- -//////////////////////////////////////////////////////////////////////////////// -/// @brief global VocBase -//////////////////////////////////////////////////////////////////////////////// - -TRI_vocbase_t* GlobalVocbase = 0; - //////////////////////////////////////////////////////////////////////////////// /// @brief global V8 dealer //////////////////////////////////////////////////////////////////////////////// @@ -1153,7 +1148,7 @@ static v8::Handle JS_ClusterTest (v8::Arguments const& argv) { #endif //////////////////////////////////////////////////////////////////////////////// -/// @brief defines and executes a periodic task +/// @brief defines and executes a task /// /// @FUN{internal.executeTask(@FA{task})} //////////////////////////////////////////////////////////////////////////////// @@ -1203,70 +1198,52 @@ static v8::Handle JS_ExecuteTask (v8::Arguments const& argv) { // period in seconds & count double period = 0.0; - int64_t count; if (obj->HasOwnProperty(TRI_V8_SYMBOL("period"))) { period = TRI_ObjectToDouble(obj->Get(TRI_V8_SYMBOL("period"))); + } + - if (period <= 0.0) { - TRI_V8_EXCEPTION_PARAMETER(scope, "task period must be specified and positive"); - } + if (period <= 0.0) { + TRI_V8_EXCEPTION_PARAMETER(scope, "task period must be specified and positive"); + } - if (obj->HasOwnProperty(TRI_V8_SYMBOL("count"))) { - // check for count attribute - count = TRI_ObjectToInt64(obj->Get(TRI_V8_SYMBOL("count"))); + + // extract the command + if (! obj->HasOwnProperty(TRI_V8_SYMBOL("command"))) { + TRI_V8_EXCEPTION_PARAMETER(scope, "command must be specified"); + } - if (count <= 0) { - TRI_V8_EXCEPTION_PARAMETER(scope, "task execution count must be specified and positive"); - } - } - else { - // no count specified. this means the job will go on forever - count = -1; - } + string command; + if (obj->Get(TRI_V8_SYMBOL("command"))->IsFunction()) { + // need to add ( and ) around function because call would otherwise break + command = "(" + TRI_ObjectToString(obj->Get(TRI_V8_SYMBOL("command"))) + ")(params)"; } else { - count = 1; // single execution + command = TRI_ObjectToString(obj->Get(TRI_V8_SYMBOL("command"))); } - assert(count == -1 || count > 0); + // extract the parameters + TRI_json_t* parameters = 0; - // TODO: count is currently not used - - // extract the module name - if (! obj->HasOwnProperty(TRI_V8_SYMBOL("module"))) { - TRI_V8_EXCEPTION_PARAMETER(scope, "module must be specified"); - } - - string const module = TRI_ObjectToString(obj->Get(TRI_V8_SYMBOL("module"))); - - // extract the function name - if (! obj->HasOwnProperty(TRI_V8_SYMBOL("funcname"))) { - TRI_V8_EXCEPTION_PARAMETER(scope, "funcname must be specified"); - } - - string const func = TRI_ObjectToString(obj->Get(TRI_V8_SYMBOL("funcname"))); - - // extract the parameter - string parameter; - if (obj->HasOwnProperty(TRI_V8_SYMBOL("parameter"))) { - parameter = TRI_ObjectToString(obj->Get(TRI_V8_SYMBOL("parameter"))); + if (obj->HasOwnProperty(TRI_V8_SYMBOL("params"))) { + parameters = TRI_ObjectToJson(obj->Get(TRI_V8_SYMBOL("params"))); } + TRI_v8_global_t* v8g = (TRI_v8_global_t*) v8::Isolate::GetCurrent()->GetData(); // create a new periodic task V8PeriodicTask* task = new V8PeriodicTask( id, name, - GlobalVocbase, + static_cast(v8g->_vocbase), GlobalV8Dealer, GlobalScheduler, GlobalDispatcher, offset, period, - module, - func, - parameter); + command, + parameters); int res = GlobalScheduler->registerTask(task); @@ -1276,17 +1253,21 @@ static v8::Handle JS_ExecuteTask (v8::Arguments const& argv) { TRI_V8_EXCEPTION(scope, res); } - // return the result - v8::Handle result = v8::Object::New(); + // get the JSON representation of the task + TRI_json_t* json = task->toJson(); - result->Set(TRI_V8_SYMBOL("id"), v8::String::New(id.c_str(), (int) id.size())); - result->Set(TRI_V8_SYMBOL("name"), v8::String::New(name.c_str(), (int) name.size())); + if (json != 0) { + v8::Handle result = TRI_ObjectJson(json); + TRI_FreeJson(TRI_UNKNOWN_MEM_ZONE, json); - return scope.Close(result); + return scope.Close(result); + } + + TRI_V8_EXCEPTION(scope, TRI_ERROR_INTERNAL); } //////////////////////////////////////////////////////////////////////////////// -/// @brief deletes a periodic task +/// @brief deletes a task /// /// @FUN{internal.deleteTask(@FA{id})} //////////////////////////////////////////////////////////////////////////////// @@ -1298,7 +1279,17 @@ static v8::Handle JS_DeleteTask (v8::Arguments const& argv) { TRI_V8_EXCEPTION_USAGE(scope, "deleteTask()"); } - string const id = TRI_ObjectToString(argv[0]); + string id; + if (argv[0]->IsObject()) { + // extract "id" from object + v8::Handle obj = argv[0].As(); + if (obj->Has(TRI_V8_SYMBOL("id"))) { + id = TRI_ObjectToString(obj->Get(TRI_V8_SYMBOL("id"))); + } + } + else { + id = TRI_ObjectToString(argv[0]); + } if (GlobalScheduler == 0 || GlobalDispatcher == 0) { TRI_V8_EXCEPTION_MESSAGE(scope, TRI_ERROR_INTERNAL, "no scheduler found"); @@ -1314,7 +1305,7 @@ static v8::Handle JS_DeleteTask (v8::Arguments const& argv) { } //////////////////////////////////////////////////////////////////////////////// -/// @brief gets all registered periodic tasks +/// @brief gets all registered tasks /// /// @FUN{internal.getTasks()} //////////////////////////////////////////////////////////////////////////////// @@ -1357,7 +1348,6 @@ void TRI_InitV8Actions (v8::Handle context, ApplicationV8* applicationV8) { v8::HandleScope scope; - GlobalVocbase = vocbase; GlobalV8Dealer = applicationV8; // check the isolate diff --git a/js/apps/system/aardvark/frontend/js/bootstrap/module-internal.js b/js/apps/system/aardvark/frontend/js/bootstrap/module-internal.js index 899cd7a71f..3182b42bf5 100644 --- a/js/apps/system/aardvark/frontend/js/bootstrap/module-internal.js +++ b/js/apps/system/aardvark/frontend/js/bootstrap/module-internal.js @@ -758,6 +758,13 @@ if (typeof SYS_EXECUTE_TASK !== "undefined") { exports.executeTask = SYS_EXECUTE_TASK; delete SYS_EXECUTE_TASK; + + // TODO: remove this in next release + exports.definePeriodic = function (offset, period, module, funcname) { + require("console").warn("definePeriodic() is deprecated. please use executeTask() instead"); + var command = "require('" + module + "')." + funcname + "();" + exports.executeTask({ offset: offset, period: period, command: command }); + }; } //////////////////////////////////////////////////////////////////////////////// diff --git a/js/common/bootstrap/module-internal.js b/js/common/bootstrap/module-internal.js index 899cd7a71f..fe471110a3 100644 --- a/js/common/bootstrap/module-internal.js +++ b/js/common/bootstrap/module-internal.js @@ -758,6 +758,13 @@ if (typeof SYS_EXECUTE_TASK !== "undefined") { exports.executeTask = SYS_EXECUTE_TASK; delete SYS_EXECUTE_TASK; + + // TODO: remove this in next release + exports.definePeriodic = function (offset, period, module, funcname) { + require("console").warn("definePeriodic() is deprecated. please use executeTask() instead"); + var command = "require('" + module + "')." + funcname + "();"; + exports.executeTask({ offset: offset, period: period, command: command }); + }; } //////////////////////////////////////////////////////////////////////////////// diff --git a/js/common/modules/jslint/jslint.new.js b/js/common/modules/jslint/jslint.new.js new file mode 100644 index 0000000000..ac9721b282 --- /dev/null +++ b/js/common/modules/jslint/jslint.new.js @@ -0,0 +1,4277 @@ +// jslint.js +// 2014-02-06 + +// 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. + +// WARNING: JSLint will hurt your feelings. + +// 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 a JSON text. + +// The second parameter is an optional object of options that control the +// operation of JSLINT. Most of the options are booleans: They are all +// optional and have a default value of false. One of the options, predef, +// can be an array of names, which will be used to declare global variables, +// or an object whose keys are used as global names, with a boolean value +// that determines if they are assignable. + +// 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 properties: + +// { +// 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 stopping error was found, a null will be the last element of the +// JSLINT.errors array. A stopping error means that JSLint was not confident +// enough to continue. It does not necessarily mean that the error was +// especially heinous. + +// You can request a data structure that 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, +// level: NUMBER, +// parameter: [ +// STRING +// ], +// var: [ +// STRING +// ], +// exception: [ +// STRING +// ], +// closure: [ +// STRING +// ], +// outer: [ +// STRING +// ], +// global: [ +// STRING +// ], +// label: [ +// STRING +// ] +// } +// ], +// global: [ +// STRING +// ], +// member: { +// STRING: NUMBER +// }, +// json: BOOLEAN +// } + +// 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 into an HTML . It should be given the result of the +// JSLINT.data function. + +// var myReport = JSLINT.report(data); + +// You can request an HTML error report. + +// var myErrorReport = JSLINT.error_report(data); + +// You can obtain an object containing all of the properties found in the +// file. JSLINT.property contains an object containing a key for each +// property used in the program, the value being the number of times that +// property name was used in the file. + +// You can request a properties report, which produces a list of the program's +// properties in the form of a /*properties*/ declaration. + +// var myPropertyReport = JSLINT.properties_report(JSLINT.property); + +// You can obtain the parse tree that JSLint constructed while parsing. The +// latest tree is kept in JSLINT.tree. A nice stringification can be produced +// with + +// JSON.stringify(JSLINT.tree, [ +// 'string', 'arity', 'name', 'first', +// 'second', 'third', 'block', 'else' +// ], 4)); + +// You can request a context coloring table. It contains information that can be +// applied to the file that was analyzed. Context coloring colors functions +// based on their nesting level, and variables on the color of the functions +// in which they are defined. + +// var myColorization = JSLINT.color(data); + +// It returns an array containing objects of this form: + +// { +// from: COLUMN, +// thru: COLUMN, +// line: ROW, +// level: 0 or higher +// } + +// JSLint provides three inline directives. They look like slashstar comments, +// and allow for setting options, declaring global variables, and establishing a +// set of allowed property names. + +// These directives respect function scope. + +// The jslint directive is a special comment that can set one or more options. +// For example: + +/*jslint + evil: true, nomen: true, regexp: true, todo: true +*/ + +// The current option set is + +// ass true, if assignment expressions should be allowed +// bitwise true, if bitwise operators should be allowed +// browser true, if the standard browser globals should be predefined +// closure true, if Google Closure idioms should be tolerated +// continue true, if the continuation statement should be tolerated +// debug true, if debugger statements should be allowed +// devel true, if logging should be allowed (console, alert, etc.) +// eqeq true, if == should be allowed +// evil true, if eval should be allowed +// forin true, if for in statements need not filter +// indent the indentation factor +// maxerr the maximum number of errors to allow +// maxlen the maximum length of a source line +// newcap true, if constructor names capitalization is ignored +// node true, if Node.js globals should be predefined +// nomen true, if names may have dangling _ +// passfail true, if the scan should stop on first error +// plusplus true, if increment/decrement should be allowed +// properties true, if all property names must be declared with /*properties*/ +// regexp true, if the . should be allowed in regexp literals +// rhino true, if the Rhino environment globals should be predefined +// unparam true, if unused parameters should be tolerated +// sloppy true, if the 'use strict'; pragma is optional +// stupid true, if really stupid practices are tolerated +// sub true, if all forms of subscript notation are tolerated +// todo true, if TODO comments are tolerated +// vars true, if multiple var statements per function should be allowed +// white true, if sloppy whitespace is tolerated + +// The properties directive declares an exclusive list of property names. +// Any properties named in the program that are not in the list will +// produce a warning. + +// For example: + +/*properties + '\b', '\t', '\n', '\f', '\r', '!', '!=', '!==', '"', '%', '\'', '(begin)', + '(error)', '*', '+', '-', '/', '<', '<=', '==', '===', '>', '>=', '\\', a, + a_label, a_scope, already_defined, and, apply, arguments, arity, ass, + assign, assignment_expression, assignment_function_expression, at, avoid_a, + b, bad_assignment, bad_constructor, bad_in_a, bad_invocation, bad_new, + bad_number, bad_operand, bad_wrap, bitwise, block, break, breakage, browser, + c, call, charAt, charCodeAt, character, closure, code, color, combine_var, + comments, conditional_assignment, confusing_a, confusing_regexp, + constructor_name_a, continue, control_a, couch, create, d, dangling_a, data, + dead, debug, deleted, devel, disrupt, duplicate_a, edge, edition, elif, + else, empty_block, empty_case, empty_class, entityify, eqeq, error_report, + errors, evidence, evil, exception, exec, expected_a_at_b_c, expected_a_b, + expected_a_b_from_c_d, expected_id_a, expected_identifier_a, + expected_identifier_a_reserved, expected_number_a, expected_operator_a, + expected_positive_a, expected_small_a, expected_space_a_b, + expected_string_a, f, first, flag, floor, forEach, for_if, forin, from, + fromCharCode, fud, function, function_block, function_eval, function_loop, + function_statement, function_strict, functions, global, hasOwnProperty, id, + identifier, identifier_function, immed, implied_evil, indent, indexOf, + infix_in, init, insecure_a, isAlpha, isArray, isDigit, isNaN, join, jslint, + json, keys, kind, label, labeled, lbp, leading_decimal_a, led, left, length, + level, line, loopage, master, match, maxerr, maxlen, message, missing_a, + missing_a_after_b, missing_property, missing_space_a_b, missing_use_strict, + mode, move_invocation, move_var, n, name, name_function, nested_comment, + newcap, node, nomen, not, not_a_constructor, not_a_defined, not_a_function, + not_a_label, not_a_scope, not_greater, nud, number, octal_a, open, outer, + parameter, parameter_a_get_b, parameter_arguments_a, parameter_set_a, + params, paren, passfail, plusplus, pop, postscript, predef, properties, + properties_report, property, prototype, push, quote, r, radix, raw, + read_only, reason, redefinition_a_b, regexp, relation, replace, report, + reserved, reserved_a, rhino, right, scanned_a_b, scope, search, second, + shift, slash_equal, slice, sloppy, sort, split, statement, statement_block, + stop, stopping, strange_loop, strict, string, stupid, sub, subscript, + substr, supplant, sync_a, t, tag_a_in_b, test, third, thru, toString, todo, + todo_comment, token, tokens, too_long, too_many, trailing_decimal_a, tree, + unclosed, unclosed_comment, unclosed_regexp, unescaped_a, unexpected_a, + unexpected_char_a, unexpected_comment, unexpected_label_a, + unexpected_property_a, unexpected_space_a_b, unexpected_typeof_a, + uninitialized_a, unnecessary_else, unnecessary_initialize, unnecessary_use, + unparam, unreachable_a_b, unsafe, unused_a, url, use_array, use_braces, + use_nested_if, use_object, use_or, use_param, use_spaces, used, + used_before_a, var, var_a_not, var_loop, vars, varstatement, warn, warning, + was, weird_assignment, weird_condition, weird_new, weird_program, + weird_relation, weird_ternary, white, wrap, wrap_immediate, wrap_regexp, + write_is_wrong, writeable +*/ + +// The global directive is used to declare global variables that can +// be accessed by the program. If a declaration is true, then the variable +// is writeable. Otherwise, it is read-only. + +// 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 JSLINT function itself. That function is also an object that +// can contain data and other functions. + +var JSLINT = exports.JSLINT = (function () { + 'use strict'; + + function array_to_object(array, value) { + +// Make an object from an array of keys and a common value. + + var i, length = array.length, object = Object.create(null); + for (i = 0; i < length; i += 1) { + object[array[i]] = value; + } + return object; + } + + + var allowed_option = { + ass : true, + bitwise : true, + browser : true, + closure : true, + continue : true, + couch : true, + debug : true, + devel : true, + eqeq : true, + evil : true, + forin : true, + indent : 10, + maxerr : 1000, + maxlen : 256, + newcap : true, + node : true, + nomen : true, + passfail : true, + plusplus : true, + properties: true, + regexp : true, + rhino : true, + unparam : true, + sloppy : true, + stupid : true, + sub : true, + todo : true, + vars : true, + white : true + }, + anonname, // The guessed name for anonymous functions. + +// 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 + }, + begin, // The root token + block_var, // vars defined in the current block + +// browser contains a set of global names that are commonly provided by a +// web browser environment. + + browser = array_to_object([ + 'clearInterval', 'clearTimeout', 'document', 'event', 'FormData', + 'frames', 'history', 'Image', 'localStorage', 'location', 'name', + 'navigator', 'Option', 'parent', 'screen', 'sessionStorage', + 'setInterval', 'setTimeout', 'Storage', 'window', 'XMLHttpRequest' + ], false), + +// bundle contains the text messages. + + bundle = { + a_label: "'{a}' is a statement label.", + a_scope: "'{a}' used out of scope.", + already_defined: "'{a}' is already defined.", + and: "The '&&' subexpression should be wrapped in parens.", + assignment_expression: "Unexpected assignment expression.", + assignment_function_expression: "Expected an assignment or " + + "function call and instead saw an expression.", + avoid_a: "Avoid '{a}'.", + bad_assignment: "Bad assignment.", + bad_constructor: "Bad constructor.", + bad_in_a: "Bad for in variable '{a}'.", + bad_invocation: "Bad invocation.", + bad_new: "Do not use 'new' for side effects.", + bad_number: "Bad number '{a}'.", + bad_operand: "Bad operand.", + bad_wrap: "Do not wrap function literals in parens unless they " + + "are to be immediately invoked.", + combine_var: "Combine this with the previous 'var' statement.", + conditional_assignment: "Expected a conditional expression and " + + "instead saw an assignment.", + confusing_a: "Confusing use of '{a}'.", + confusing_regexp: "Confusing regular expression.", + constructor_name_a: "A constructor name '{a}' should start with " + + "an uppercase letter.", + control_a: "Unexpected control character '{a}'.", + dangling_a: "Unexpected dangling '_' in '{a}'.", + deleted: "Only properties should be deleted.", + duplicate_a: "Duplicate '{a}'.", + empty_block: "Empty block.", + empty_case: "Empty case.", + empty_class: "Empty class.", + evil: "eval is evil.", + expected_a_b: "Expected '{a}' and instead saw '{b}'.", + expected_a_b_from_c_d: "Expected '{a}' to match '{b}' from line " + + "{c} and instead saw '{d}'.", + expected_a_at_b_c: "Expected '{a}' at column {b}, not column {c}.", + expected_id_a: "Expected an id, and instead saw #{a}.", + expected_identifier_a: "Expected an identifier and instead saw '{a}'.", + expected_identifier_a_reserved: "Expected an identifier and " + + "instead saw '{a}' (a reserved word).", + expected_number_a: "Expected a number and instead saw '{a}'.", + expected_operator_a: "Expected an operator and instead saw '{a}'.", + expected_positive_a: "Expected a positive number and instead saw '{a}'", + expected_small_a: "Expected a small positive integer and instead saw '{a}'", + expected_space_a_b: "Expected exactly one space between '{a}' and '{b}'.", + expected_string_a: "Expected a string and instead saw '{a}'.", + for_if: "The body of a for in should be wrapped in an if " + + "statement to filter unwanted properties from the prototype.", + function_block: "Function statements should not be placed in blocks." + + "Use a function expression or move the statement to the top of " + + "the outer function.", + function_eval: "The Function constructor is eval.", + function_loop: "Don't make functions within a loop.", + function_statement: "Function statements are not invocable. " + + "Wrap the whole function invocation in parens.", + function_strict: "Use the function form of 'use strict'.", + identifier_function: "Expected an identifier in an assignment " + + "and instead saw a function invocation.", + implied_evil: "Implied eval is evil. Pass a function instead of a string.", + infix_in: "Unexpected 'in'. Compare with undefined, or use the " + + "hasOwnProperty method instead.", + insecure_a: "Insecure '{a}'.", + isNaN: "Use the isNaN function to compare with NaN.", + leading_decimal_a: "A leading decimal point can be confused with a dot: '.{a}'.", + missing_a: "Missing '{a}'.", + missing_a_after_b: "Missing '{a}' after '{b}'.", + missing_property: "Missing property name.", + missing_space_a_b: "Missing space between '{a}' and '{b}'.", + missing_use_strict: "Missing 'use strict' statement.", + move_invocation: "Move the invocation into the parens that " + + "contain the function.", + move_var: "Move 'var' declarations to the top of the function.", + name_function: "Missing name in function statement.", + nested_comment: "Nested comment.", + not: "Nested not.", + not_a_constructor: "Do not use {a} as a constructor.", + not_a_defined: "'{a}' has not been fully defined yet.", + not_a_function: "'{a}' is not a function.", + not_a_label: "'{a}' is not a label.", + not_a_scope: "'{a}' is out of scope.", + not_greater: "'{a}' should not be greater than '{b}'.", + octal_a: "Don't use octal: '{a}'. Use '\\u....' instead.", + parameter_arguments_a: "Do not mutate parameter '{a}' when using 'arguments'.", + parameter_a_get_b: "Unexpected parameter '{a}' in get {b} function.", + parameter_set_a: "Expected parameter (value) in set {a} function.", + radix: "Missing radix parameter.", + read_only: "Read only.", + redefinition_a_b: "Redefinition of '{a}' from line {b}.", + reserved_a: "Reserved name '{a}'.", + scanned_a_b: "{a} ({b}% scanned).", + slash_equal: "A regular expression literal can be confused with '/='.", + statement_block: "Expected to see a statement and instead saw a block.", + stopping: "Stopping.", + strange_loop: "Strange loop.", + strict: "Strict violation.", + subscript: "['{a}'] is better written in dot notation.", + sync_a: "Unexpected sync method: '{a}'.", + tag_a_in_b: "A '<{a}>' must be within '<{b}>'.", + todo_comment: "Unexpected TODO comment.", + too_long: "Line too long.", + too_many: "Too many errors.", + trailing_decimal_a: "A trailing decimal point can be confused " + + "with a dot: '.{a}'.", + unclosed: "Unclosed string.", + unclosed_comment: "Unclosed comment.", + unclosed_regexp: "Unclosed regular expression.", + unescaped_a: "Unescaped '{a}'.", + unexpected_a: "Unexpected '{a}'.", + unexpected_char_a: "Unexpected character '{a}'.", + unexpected_comment: "Unexpected comment.", + unexpected_label_a: "Unexpected label '{a}'.", + unexpected_property_a: "Unexpected /*property*/ '{a}'.", + unexpected_space_a_b: "Unexpected space between '{a}' and '{b}'.", + unexpected_typeof_a: "Unexpected 'typeof'. " + + "Use '===' to compare directly with {a}.", + uninitialized_a: "Uninitialized '{a}'.", + unnecessary_else: "Unnecessary 'else' after disruption.", + unnecessary_initialize: "It is not necessary to initialize '{a}' " + + "to 'undefined'.", + unnecessary_use: "Unnecessary 'use strict'.", + unreachable_a_b: "Unreachable '{a}' after '{b}'.", + unsafe: "Unsafe character.", + unused_a: "Unused '{a}'.", + url: "JavaScript URL.", + use_array: "Use the array literal notation [].", + use_braces: "Spaces are hard to count. Use {{a}}.", + use_nested_if: "Expected 'else { if' and instead saw 'else if'.", + use_object: "Use the object literal notation {} or Object.create(null).", + use_or: "Use the || operator.", + use_param: "Use a named parameter.", + use_spaces: "Use spaces, not tabs.", + used_before_a: "'{a}' was used before it was defined.", + var_a_not: "Variable {a} was not declared correctly.", + var_loop: "Don't declare variables in a loop.", + weird_assignment: "Weird assignment.", + weird_condition: "Weird condition.", + weird_new: "Weird construction. Delete 'new'.", + weird_program: "Weird program.", + weird_relation: "Weird relation.", + weird_ternary: "Weird ternary.", + wrap_immediate: "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.", + wrap_regexp: "Wrap the /regexp/ literal in parens to " + + "disambiguate the slash operator.", + write_is_wrong: "document.write can be a form of eval." + }, + closure = array_to_object([ + 'goog' + ], false), + comments, + comments_off, + couch = array_to_object([ + 'emit', 'getRow', 'isArray', 'log', 'provides', 'registerType', + 'require', 'send', 'start', 'sum', 'toJSON' + ], false), + + descapes = { + 'b': '\b', + 't': '\t', + 'n': '\n', + 'f': '\f', + 'r': '\r', + '"': '"', + '/': '/', + '\\': '\\', + '!': '!' + }, + + devel = array_to_object([ + 'alert', 'confirm', 'console', 'Debug', 'opera', 'prompt', 'WSH' + ], false), + directive, + escapes = { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\'': '\\\'', + '"' : '\\"', + '/' : '\\/', + '\\': '\\\\' + }, + + funct, // The current function + + functions, // All of the functions + global_funct, // The global body + global_scope, // The global scope + in_block, // Where function statements are not allowed + indent, + itself, // JSLINT itself + json_mode, + lex, // the tokenizer + lines, + lookahead, + node = array_to_object([ + 'Buffer', 'clearImmediate', 'clearInterval', 'clearTimeout', + 'console', 'exports', 'global', 'module', 'process', 'querystring', + 'require', 'setImmediate', 'setInterval', 'setTimeout', + '__dirname', '__filename' + ], false), + node_js, + numbery = array_to_object(['indexOf', 'lastIndexOf', 'search'], true), + next_token, + option, + predefined, // Global variables defined by option + prereg, + prev_token, + property, + protosymbol, + regexp_flag = array_to_object(['g', 'i', 'm'], true), + return_this = function return_this() { + return this; + }, + rhino = array_to_object([ + 'defineClass', 'deserialize', 'gc', 'help', 'load', 'loadClass', + 'print', 'quit', 'readFile', 'readUrl', 'runCommand', 'seal', + 'serialize', 'spawn', 'sync', 'toint32', 'version' + ], false), + + scope, // An object containing an object for each variable in scope + semicolon_coda = array_to_object([';', '"', '\'', ')'], true), + +// standard contains the global names that are provided by the +// ECMAScript standard. + + standard = array_to_object([ + 'Array', 'Boolean', 'Date', 'decodeURI', 'decodeURIComponent', + 'encodeURI', 'encodeURIComponent', 'Error', 'eval', 'EvalError', + 'Function', 'isFinite', 'isNaN', 'JSON', 'Map', 'Math', 'Number', + 'Object', 'parseInt', 'parseFloat', 'Promise', 'Proxy', + 'RangeError', 'ReferenceError', 'Reflect', 'RegExp', 'Set', + 'String', 'Symbol', 'SyntaxError', 'System', 'TypeError', + 'URIError', 'WeakMap', 'WeakSet' + ], false), + + strict_mode, + syntax = Object.create(null), + token, + tokens, + var_mode, + warnings, + +// Regular expressions. Some of these are stupidly long. + +// carriage return, carriage return linefeed, or linefeed + crlfx = /\r\n?|\n/, +// unsafe characters that are silently deleted by one or more browsers + cx = /[\u0000-\u0008\u000a-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/, +// identifier + ix = /^([a-zA-Z_$][a-zA-Z0-9_$]*)$/, +// javascript url + jx = /^(?:javascript|jscript|ecmascript|vbscript)\s*:/i, +// star slash + lx = /\*\/|\/\*/, +// 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]/g, +// sync + syx = /Sync$/, +// comment todo + tox = /^\W*to\s*do(?:\W|$)/i, +// token + tx = /^\s*([(){}\[\]\?.,:;'"~#@`]|={1,3}|\/(\*(jslint|properties|property|members?|globals?)?|=|\/)?|\*[\/=]?|\+(?:=|\++)?|-(?:=|-+)?|[\^%]=?|&[&=]?|\|[|=]?|>{1,3}=?|<(?:[\/=!]|\!(\[|--)?|<=?)?|\!(\!|==?)?|[a-zA-Z_$][a-zA-Z0-9_$]*|[0-9]+(?:[xX][0-9a-fA-F]+|\.[0-9]*)?(?:[eE][+\-]?[0-9]+)?)/; + + + 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 replacement = o[b]; + return typeof replacement === 'string' || + typeof replacement === 'number' ? replacement : a; + }); + }; + } + + + function sanitize(a) { + +// Escapify a troublesome character. + + return escapes[a] || + '\\u' + ('0000' + a.charCodeAt().toString(16)).slice(-4); + } + + + function add_to_predefined(group) { + Object.keys(group).forEach(function (name) { + predefined[name] = group[name]; + }); + } + + + function assume() { + if (option.browser) { + add_to_predefined(browser); + option.browser = false; + } + if (option.closure) { + add_to_predefined(closure); + } + if (option.couch) { + add_to_predefined(couch); + option.couch = false; + } + if (option.devel) { + add_to_predefined(devel); + option.devel = false; + } + if (option.node) { + add_to_predefined(node); + option.node = false; + node_js = true; + } + if (option.rhino) { + add_to_predefined(rhino); + option.rhino = false; + } + } + + +// Produce an error warning. + + function artifact(tok) { + if (!tok) { + tok = next_token; + } + return tok.id === '(number)' ? tok.number : tok.string; + } + + function quit(message, line, character) { + throw { + name: 'JSLintError', + line: line, + character: character, + message: bundle.scanned_a_b.supplant({ + a: bundle[message] || message, + b: Math.floor((line / lines.length) * 100) + }) + }; + } + + function warn(code, line, character, a, b, c, d) { + var warning = { // ~~ + id: '(error)', + raw: bundle[code] || code, + code: code, + evidence: lines[line - 1] || '', + line: line, + character: character, + a: a || artifact(this), + b: b, + c: c, + d: d + }; + warning.reason = warning.raw.supplant(warning); + itself.errors.push(warning); + if (option.passfail) { + quit('stopping', line, character); + } + warnings += 1; + if (warnings >= option.maxerr) { + quit('too_many', line, character); + } + return warning; + } + + function stop(code, line, character, a, b, c, d) { + var warning = warn(code, line, character, a, b, c, d); + quit('stopping', warning.line, warning.character); + } + + function expected_at(at) { + if (!option.white && next_token.from !== at) { + next_token.warn('expected_a_at_b_c', '', at, next_token.from); + } + } + +// lexical analysis and token construction + + lex = (function lex() { + var character, c, from, length, line, pos, source_row; + +// Private lex methods + + function next_line() { + var at; + character = 1; + source_row = lines[line]; + line += 1; + if (source_row === undefined) { + return false; + } + at = source_row.search(/\t/); + if (at >= 0) { + if (option.white) { + source_row = source_row.replace(/\t/g, ' '); + } else { + warn('use_spaces', line, at + 1); + } + } + at = source_row.search(cx); + if (at >= 0) { + warn('unsafe', line, at); + } + if (option.maxlen && option.maxlen < source_row.length) { + warn('too_long', line, source_row.length); + } + return true; + } + +// Produce a token object. The token inherits from a syntax symbol. + + function it(type, value) { + var id, the_token; + if (type === '(string)') { + if (jx.test(value)) { + warn('url', line, from); + } + } + the_token = Object.create(syntax[( + type === '(punctuator)' || (type === '(identifier)' && + Object.prototype.hasOwnProperty.call(syntax, value)) + ? value + : type + )] || syntax['(error)']); + if (type === '(identifier)') { + the_token.identifier = true; + if (value === '__iterator__' || value === '__proto__') { + stop('reserved_a', line, from, value); + } else if (!option.nomen && + (value.charAt(0) === '_' || + value.charAt(value.length - 1) === '_')) { + warn('dangling_a', line, from, value); + } + } + if (type === '(number)') { + the_token.number = +value; + } else if (value !== undefined) { + the_token.string = String(value); + } + the_token.line = line; + the_token.from = from; + the_token.thru = character; + if (comments.length) { + the_token.comments = comments; + comments = []; + } + id = the_token.id; + prereg = id && ( + ('(,=:[!&|?{};~+-*%^<>'.indexOf(id.charAt(id.length - 1)) >= 0) || + id === 'return' || id === 'case' + ); + return the_token; + } + + function match(x) { + var exec = x.exec(source_row), first; + if (exec) { + length = exec[0].length; + first = exec[1]; + c = first.charAt(0); + source_row = source_row.slice(length); + from = character + length - first.length; + character += length; + return first; + } + for (;;) { + if (!source_row) { + if (!option.white) { + warn('unexpected_char_a', line, character - 1, '(space)'); + } + return; + } + c = source_row.charAt(0); + if (c !== ' ') { + break; + } + source_row = source_row.slice(1); + character += 1; + } + stop('unexpected_char_a', line, character, c); + + } + + function string(x) { + var ch, at = 0, r = '', result; + + function hex(n) { + var i = parseInt(source_row.substr(at + 1, n), 16); + at += n; + if (i >= 32 && i <= 126 && + i !== 34 && i !== 92 && i !== 39) { + warn('unexpected_a', line, character, '\\'); + } + character += n; + ch = String.fromCharCode(i); + } + + if (json_mode && x !== '"') { + warn('expected_a_b', line, character, '"', x); + } + + for (;;) { + while (at >= source_row.length) { + at = 0; + if (!next_line()) { + stop('unclosed', line - 1, from); + } + } + ch = source_row.charAt(at); + if (ch === x) { + character += 1; + source_row = source_row.slice(at + 1); + result = it('(string)', r); + result.quote = x; + return result; + } + if (ch < ' ') { + if (ch === '\n' || ch === '\r') { + break; + } + warn('control_a', line, character + at, + source_row.slice(0, at)); + } else if (ch === '\\') { + at += 1; + character += 1; + ch = source_row.charAt(at); + switch (ch) { + case '': + warn('unexpected_a', line, character, '\\'); + next_line(); + at = -1; + break; + case '\'': + if (json_mode) { + warn('unexpected_a', line, character, '\\\''); + } + break; + case 'u': + hex(4); + break; + case 'v': + if (json_mode) { + warn('unexpected_a', line, character, '\\v'); + } + ch = '\v'; + break; + case 'x': + if (json_mode) { + warn('unexpected_a', line, character, '\\x'); + } + hex(2); + break; + default: + if (typeof descapes[ch] !== 'string') { + warn(ch >= '0' && ch <= '7' ? 'octal_a' : 'unexpected_a', + line, character, '\\' + ch); + } else { + ch = descapes[ch]; + } + } + } + r += ch; + character += 1; + at += 1; + } + } + + function number(snippet) { + var digit; + if (source_row.charAt(0).isAlpha()) { + warn('expected_space_a_b', + line, character, c, source_row.charAt(0)); + } + if (c === '0') { + digit = snippet.charAt(1); + if (digit.isDigit()) { + if (token.id !== '.') { + warn('unexpected_a', line, character, snippet); + } + } else if (json_mode && (digit === 'x' || digit === 'X')) { + warn('unexpected_a', line, character, '0x'); + } + } + if (snippet.slice(snippet.length - 1) === '.') { + warn('trailing_decimal_a', line, character, snippet); + } + digit = +snippet; + if (!isFinite(digit)) { + warn('bad_number', line, character, snippet); + } + snippet = digit; + return it('(number)', snippet); + } + + function comment(snippet, type) { + if (comments_off) { + warn('unexpected_comment', line, character); + } else if (!option.todo && tox.test(snippet)) { + warn('todo_comment', line, character); + } + comments.push({ + id: type, + from: from, + thru: character, + line: line, + string: snippet + }); + } + + function regexp() { + var at = 0, + b, + bit, + depth = 0, + flag = '', + high, + letter, + low, + potential, + quote, + result; + for (;;) { + b = true; + c = source_row.charAt(at); + at += 1; + switch (c) { + case '': + stop('unclosed_regexp', line, from); + return; + case '/': + if (depth > 0) { + warn('unescaped_a', line, from + at, '/'); + } + c = source_row.slice(0, at - 1); + potential = Object.create(regexp_flag); + for (;;) { + letter = source_row.charAt(at); + if (potential[letter] !== true) { + break; + } + potential[letter] = false; + at += 1; + flag += letter; + } + if (source_row.charAt(at).isAlpha()) { + stop('unexpected_a', line, from, source_row.charAt(at)); + } + character += at; + source_row = source_row.slice(at); + quote = source_row.charAt(0); + if (quote === '/' || quote === '*') { + stop('confusing_regexp', line, from); + } + result = it('(regexp)', c); + result.flag = flag; + return result; + case '\\': + c = source_row.charAt(at); + if (c < ' ') { + warn('control_a', line, from + at, String(c)); + } else if (c === '<') { + warn('unexpected_a', line, from + at, '\\'); + } + at += 1; + break; + case '(': + depth += 1; + b = false; + if (source_row.charAt(at) === '?') { + at += 1; + switch (source_row.charAt(at)) { + case ':': + case '=': + case '!': + at += 1; + break; + default: + warn('expected_a_b', line, from + at, + ':', source_row.charAt(at)); + } + } + break; + case '|': + b = false; + break; + case ')': + if (depth === 0) { + warn('unescaped_a', line, from + at, ')'); + } else { + depth -= 1; + } + break; + case ' ': + pos = 1; + while (source_row.charAt(at) === ' ') { + at += 1; + pos += 1; + } + if (pos > 1) { + warn('use_braces', line, from + at, pos); + } + break; + case '[': + c = source_row.charAt(at); + if (c === '^') { + at += 1; + if (!option.regexp) { + warn('insecure_a', line, from + at, c); + } else if (source_row.charAt(at) === ']') { + stop('unescaped_a', line, from + at, '^'); + } + } + bit = false; + if (c === ']') { + warn('empty_class', line, from + at - 1); + bit = true; + } +klass: do { + c = source_row.charAt(at); + at += 1; + switch (c) { + case '[': + case '^': + warn('unescaped_a', line, from + at, c); + bit = true; + break; + case '-': + if (bit) { + bit = false; + } else { + warn('unescaped_a', line, from + at, '-'); + bit = true; + } + break; + case ']': + if (!bit) { + warn('unescaped_a', line, from + at - 1, '-'); + } + break klass; + case '\\': + c = source_row.charAt(at); + if (c < ' ') { + warn('control_a', line, from + at, String(c)); + } else if (c === '<') { + warn('unexpected_a', line, from + at, '\\'); + } + at += 1; + bit = true; + break; + case '/': + warn('unescaped_a', line, from + at - 1, '/'); + bit = true; + break; + default: + bit = true; + } + } while (c); + break; + case '.': + if (!option.regexp) { + warn('insecure_a', line, from + at, c); + } + break; + case ']': + case '?': + case '{': + case '}': + case '+': + case '*': + warn('unescaped_a', line, from + at, c); + break; + } + if (b) { + switch (source_row.charAt(at)) { + case '?': + case '+': + case '*': + at += 1; + if (source_row.charAt(at) === '?') { + at += 1; + } + break; + case '{': + at += 1; + c = source_row.charAt(at); + if (c < '0' || c > '9') { + warn('expected_number_a', line, + from + at, c); + } + at += 1; + low = +c; + for (;;) { + c = source_row.charAt(at); + if (c < '0' || c > '9') { + break; + } + at += 1; + low = +c + (low * 10); + } + high = low; + if (c === ',') { + at += 1; + high = Infinity; + c = source_row.charAt(at); + if (c >= '0' && c <= '9') { + at += 1; + high = +c; + for (;;) { + c = source_row.charAt(at); + if (c < '0' || c > '9') { + break; + } + at += 1; + high = +c + (high * 10); + } + } + } + if (source_row.charAt(at) !== '}') { + warn('expected_a_b', line, from + at, + '}', c); + } else { + at += 1; + } + if (source_row.charAt(at) === '?') { + at += 1; + } + if (low > high) { + warn('not_greater', line, from + at, + low, high); + } + break; + } + } + } + c = source_row.slice(0, at - 1); + character += at; + source_row = source_row.slice(at); + return it('(regexp)', c); + } + +// Public lex methods + + return { + init: function (source) { + if (typeof source === 'string') { + lines = source.split(crlfx); + } else { + lines = source; + } + line = 0; + next_line(); + from = 1; + }, + +// token -- this is called by advance to get the next token. + + token: function () { + var first, i, snippet; + + for (;;) { + while (!source_row) { + if (!next_line()) { + return it('(end)'); + } + } + snippet = match(tx); + if (snippet) { + +// identifier + + first = snippet.charAt(0); + if (first.isAlpha() || first === '_' || first === '$') { + return it('(identifier)', snippet); + } + +// number + + if (first.isDigit()) { + return number(snippet); + } + switch (snippet) { + +// string + + case '"': + case "'": + return string(snippet); + +// // comment + + case '//': + comment(source_row, '//'); + source_row = ''; + break; + +// /* comment + + case '/*': + for (;;) { + i = source_row.search(lx); + if (i >= 0) { + break; + } + character = source_row.length; + comment(source_row); + from = 0; + if (!next_line()) { + stop('unclosed_comment', line, character); + } + } + comment(source_row.slice(0, i), '/*'); + character += i + 2; + if (source_row.charAt(i) === '/') { + stop('nested_comment', line, character); + } + source_row = source_row.slice(i + 2); + break; + + case '': + break; +// / + case '/': + if (token.id === '/=') { + stop('slash_equal', line, from); + } + return prereg + ? regexp() + : it('(punctuator)', snippet); + +// punctuator + default: + return it('(punctuator)', snippet); + } + } + } + } + }; + }()); + + function define(kind, token) { + +// Define a name. + + var name = token.string, + master = scope[name]; // The current definition of the name + +// vars are created with a deadzone, so that the expression that initializes +// the var cannot access the var. Functions are not writeable. + + token.dead = false; + token.init = false; + token.kind = kind; + token.master = master; + token.used = 0; + token.writeable = true; + +// Global variables are a little weird. They can be defined multiple times. +// Some predefined global vars are (or should) not be writeable. + + if (kind === 'var' && funct === global_funct) { + if (!master) { + if (predefined[name] === false) { + token.writeable = false; + } + global_scope[name] = token; + } + } else { + +// It is an error if the name has already been defined in this scope, except +// when reusing an exception variable name. + + if (master) { + if (master.function === funct) { + if (master.kind !== 'exception' || kind !== 'exception' || + !master.dead) { + token.warn('already_defined', name); + } + } else if (master.function !== global_funct) { + if (kind === 'var') { + token.warn('redefinition_a_b', name, master.line); + } + } + } + scope[name] = token; + if (kind === 'var') { + block_var.push(name); + } + } + } + + function peek(distance) { + +// Peek ahead to a future token. The distance is how far ahead to look. The +// default is the next token. + + var found, slot = 0; + + distance = distance || 0; + while (slot <= distance) { + found = lookahead[slot]; + if (!found) { + found = lookahead[slot] = lex.token(); + } + slot += 1; + } + return found; + } + + + function advance(id, match) { + +// Produce the next token, also looking for programming errors. + + if (indent) { + +// If indentation checking was requested, then inspect all of the line breakings. +// The var statement is tricky because the names might be aligned or not. We +// look at the first line break after the var to determine the programmer's +// intention. + + if (var_mode && next_token.line !== token.line) { + if ((var_mode !== indent || !next_token.edge) && + next_token.from === indent.at - + (next_token.edge ? option.indent : 0)) { + var dent = indent; + for (;;) { + dent.at -= option.indent; + if (dent === var_mode) { + break; + } + dent = dent.was; + } + dent.open = false; + } + var_mode = null; + } + if (next_token.id === '?' && indent.mode === ':' && + token.line !== next_token.line) { + indent.at -= option.indent; + } + if (indent.open) { + +// If the token is an edge. + + if (next_token.edge) { + if (next_token.edge === 'label') { + expected_at(1); + } else if (next_token.edge === 'case' || indent.mode === 'statement') { + expected_at(indent.at - option.indent); + } else if (indent.mode !== 'array' || next_token.line !== token.line) { + expected_at(indent.at); + } + +// If the token is not an edge, but is the first token on the line. + + } else if (next_token.line !== token.line) { + if (next_token.from < indent.at + (indent.mode === + 'expression' ? 0 : option.indent)) { + expected_at(indent.at + option.indent); + } + indent.wrap = true; + } + } else if (next_token.line !== token.line) { + if (next_token.edge) { + expected_at(indent.at); + } else { + indent.wrap = true; + if (indent.mode === 'statement' || indent.mode === 'var') { + expected_at(indent.at + option.indent); + } else if (next_token.from < indent.at + (indent.mode === + 'expression' ? 0 : option.indent)) { + expected_at(indent.at + option.indent); + } + } + } + } + + switch (token.id) { + case '(number)': + if (next_token.id === '.') { + next_token.warn('trailing_decimal_a'); + } + break; + case '-': + if (next_token.id === '-' || next_token.id === '--') { + next_token.warn('confusing_a'); + } + break; + case '+': + if (next_token.id === '+' || next_token.id === '++') { + next_token.warn('confusing_a'); + } + break; + } + if (token.id === '(string)' || token.identifier) { + anonname = token.string; + } + + if (id && next_token.id !== id) { + if (match) { + next_token.warn('expected_a_b_from_c_d', id, + match.id, match.line, artifact()); + } else if (!next_token.identifier || next_token.string !== id) { + next_token.warn('expected_a_b', id, artifact()); + } + } + prev_token = token; + token = next_token; + next_token = lookahead.shift() || lex.token(); + next_token.function = funct; + tokens.push(next_token); + } + + + function do_globals() { + var name, writeable; + for (;;) { + if (next_token.id !== '(string)' && !next_token.identifier) { + return; + } + name = next_token.string; + advance(); + writeable = false; + if (next_token.id === ':') { + advance(':'); + switch (next_token.id) { + case 'true': + writeable = predefined[name] !== false; + advance('true'); + break; + case 'false': + advance('false'); + break; + default: + next_token.stop('unexpected_a'); + } + } + predefined[name] = writeable; + if (next_token.id !== ',') { + return; + } + advance(','); + } + } + + + function do_jslint() { + var name, value; + while (next_token.id === '(string)' || next_token.identifier) { + name = next_token.string; + if (!allowed_option[name]) { + next_token.stop('unexpected_a'); + } + advance(); + if (next_token.id !== ':') { + next_token.stop('expected_a_b', ':', artifact()); + } + advance(':'); + if (typeof allowed_option[name] === 'number') { + value = next_token.number; + if (value > allowed_option[name] || value <= 0 || + Math.floor(value) !== value) { + next_token.stop('expected_small_a'); + } + option[name] = value; + } else { + if (next_token.id === 'true') { + option[name] = true; + } else if (next_token.id === 'false') { + option[name] = false; + } else { + next_token.stop('unexpected_a'); + } + } + advance(); + if (next_token.id === ',') { + advance(','); + } + } + assume(); + } + + + function do_properties() { + var name; + option.properties = true; + for (;;) { + if (next_token.id !== '(string)' && !next_token.identifier) { + return; + } + name = next_token.string; + advance(); + if (next_token.id === ':') { + for (;;) { + advance(); + if (next_token.id !== '(string)' && !next_token.identifier) { + break; + } + } + } + property[name] = 0; + if (next_token.id !== ',') { + return; + } + advance(','); + } + } + + + directive = function directive() { + var command = this.id, + old_comments_off = comments_off, + old_indent = indent; + comments_off = true; + indent = null; + if (next_token.line === token.line && next_token.from === token.thru) { + next_token.warn('missing_space_a_b', artifact(token), artifact()); + } + if (lookahead.length > 0) { + this.warn('unexpected_a'); + } + switch (command) { + case '/*properties': + case '/*property': + case '/*members': + case '/*member': + do_properties(); + break; + case '/*jslint': + do_jslint(); + break; + case '/*globals': + case '/*global': + do_globals(); + break; + default: + this.stop('unexpected_a'); + } + comments_off = old_comments_off; + advance('*/'); + indent = old_indent; + }; + + +// Indentation intention + + function edge(mode) { + next_token.edge = indent ? indent.open && (mode || 'edge') : ''; + } + + + function step_in(mode) { + var open; + if (typeof mode === 'number') { + indent = { + at: +mode, + open: true, + was: indent + }; + } else if (!indent) { + indent = { + at: 1, + mode: 'statement', + open: true + }; + } else if (mode === 'statement') { + indent = { + at: indent.at, + open: true, + was: indent + }; + } else { + open = mode === 'var' || next_token.line !== token.line; + indent = { + at: (open || mode === 'control' + ? indent.at + option.indent + : indent.at) + (indent.wrap ? option.indent : 0), + mode: mode, + open: open, + was: indent + }; + if (mode === 'var' && open) { + var_mode = indent; + } + } + } + + function step_out(id, symbol) { + if (id) { + if (indent && indent.open) { + indent.at -= option.indent; + edge(); + } + advance(id, symbol); + } + if (indent) { + indent = indent.was; + } + } + +// Functions for conformance of whitespace. + + function one_space(left, right) { + left = left || token; + right = right || next_token; + if (right.id !== '(end)' && !option.white && + (token.line !== right.line || + token.thru + 1 !== right.from)) { + right.warn('expected_space_a_b', artifact(token), artifact(right)); + } + } + + function one_space_only(left, right) { + left = left || token; + right = right || next_token; + if (right.id !== '(end)' && (left.line !== right.line || + (!option.white && left.thru + 1 !== right.from))) { + right.warn('expected_space_a_b', artifact(left), artifact(right)); + } + } + + function no_space(left, right) { + left = left || token; + right = right || next_token; + if ((!option.white) && + left.thru !== right.from && left.line === right.line) { + right.warn('unexpected_space_a_b', artifact(left), artifact(right)); + } + } + + function no_space_only(left, right) { + left = left || token; + right = right || next_token; + if (right.id !== '(end)' && (left.line !== right.line || + (!option.white && left.thru !== right.from))) { + right.warn('unexpected_space_a_b', artifact(left), artifact(right)); + } + } + + function spaces(left, right) { + if (!option.white) { + left = left || token; + right = right || next_token; + if (left.thru === right.from && left.line === right.line) { + right.warn('missing_space_a_b', artifact(left), artifact(right)); + } + } + } + + function comma() { + if (next_token.id !== ',') { + warn('expected_a_b', token.line, token.thru, ',', artifact()); + } else { + if (!option.white) { + no_space_only(); + } + advance(','); + spaces(); + } + } + + + function semicolon() { + if (next_token.id !== ';') { + warn('expected_a_b', token.line, token.thru, ';', artifact()); + } else { + if (!option.white) { + no_space_only(); + } + advance(';'); + if (semicolon_coda[next_token.id] !== true) { + spaces(); + } + } + } + + function use_strict() { + if (next_token.string === 'use strict') { + if (strict_mode) { + next_token.warn('unnecessary_use'); + } + edge(); + advance(); + semicolon(); + strict_mode = true; + return true; + } + return false; + } + + + function are_similar(a, b) { + if (a === b) { + return true; + } + if (Array.isArray(a)) { + if (Array.isArray(b) && a.length === b.length) { + var i; + for (i = 0; i < a.length; i += 1) { + if (!are_similar(a[i], b[i])) { + return false; + } + } + return true; + } + return false; + } + if (Array.isArray(b)) { + return false; + } + if (a.id === '(number)' && b.id === '(number)') { + return a.number === b.number; + } + if (a.arity === b.arity && a.string === b.string) { + switch (a.arity) { + case undefined: + return a.string === b.string; + case 'prefix': + case 'suffix': + return a.id === b.id && are_similar(a.first, b.first) && + a.id !== '{' && a.id !== '['; + case 'infix': + return are_similar(a.first, b.first) && + are_similar(a.second, b.second); + case 'ternary': + return are_similar(a.first, b.first) && + are_similar(a.second, b.second) && + are_similar(a.third, b.third); + case 'function': + case 'regexp': + return false; + default: + return true; + } + } + if (a.id === '.' && b.id === '[' && b.arity === 'infix') { + return a.second.string === b.second.string && b.second.id === '(string)'; + } + if (a.id === '[' && a.arity === 'infix' && b.id === '.') { + return a.second.string === b.second.string && a.second.id === '(string)'; + } + return false; + } + + +// This is the heart of JSLINT, 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) { + +// rbp is the right binding power. +// initial indicates that this is the first expression of a statement. + + var left; + if (next_token.id === '(end)') { + token.stop('unexpected_a', next_token.id); + } + advance(); + if (initial) { + anonname = 'anonymous'; + } + if (initial === true && token.fud) { + left = token.fud(); + } else { + if (token.nud) { + left = token.nud(); + } else { + if (next_token.id === '(number)' && token.id === '.') { + token.warn('leading_decimal_a', artifact()); + advance(); + return token; + } + token.stop('expected_identifier_a', artifact(token)); + } + while (rbp < next_token.lbp) { + advance(); + left = token.led(left); + } + } + if (left && left.assign && !initial) { + if (!option.ass) { + left.warn('assignment_expression'); + } + if (left.id !== '=' && left.first.master) { + left.first.master.used = true; + } + } + return left; + } + + protosymbol = { + nud: function () { + this.stop('unexpected_a'); + }, + led: function () { + this.stop('expected_operator_a'); + }, + warn: function (code, a, b, c, d) { + if (!this.warning) { + this.warning = warn(code, this.line || 0, this.from || 0, + a || artifact(this), b, c, d); + } + }, + stop: function (code, a, b, c, d) { + this.warning = undefined; + this.warn(code, a, b, c, d); + return quit('stopping', this.line, this.character); + }, + lbp: 0 + }; + +// Functional constructors for making the symbols that will be inherited by +// tokens. + + function symbol(s, bp) { + var x = syntax[s]; + if (!x) { + x = Object.create(protosymbol); + x.id = x.string = s; + x.lbp = bp || 0; + syntax[s] = x; + } + return x; + } + + function postscript(x) { + x.postscript = true; + return x; + } + + function ultimate(s) { + var x = symbol(s, 0); + x.from = 1; + x.thru = 1; + x.line = 0; + x.edge = 'edge'; + x.string = s; + return postscript(x); + } + + function reserve_name(x) { + var c = x.id.charAt(0); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + x.identifier = x.reserved = true; + } + return x; + } + + function stmt(s, f) { + var x = symbol(s); + x.fud = f; + return reserve_name(x); + } + + function disrupt_stmt(s, f) { + var x = stmt(s, f); + x.disrupt = true; + } + + function labeled_stmt(s, f) { + var x = stmt(s, function labeled() { + var the_statement; + if (funct.breakage) { + funct.breakage.push(this); + } else { + funct.breakage = [this]; + } + the_statement = f.apply(this); + if (funct.breakage.length > 1) { + funct.breakage.pop(); + } else { + delete funct.breakage; + } + return the_statement; + }); + x.labeled = true; + } + + function prefix(s, f) { + var x = symbol(s, 150); + reserve_name(x); + x.nud = function () { + var that = this; + that.arity = 'prefix'; + if (typeof f === 'function') { + that = f(that); + if (that.arity !== 'prefix') { + return that; + } + } else { + if (s === 'typeof') { + one_space(); + } else { + no_space_only(); + } + that.first = expression(150); + } + switch (that.id) { + case '++': + case '--': + if (!option.plusplus) { + that.warn('unexpected_a'); + } else if ((!that.first.identifier || that.first.reserved) && + that.first.id !== '.' && that.first.id !== '[') { + that.warn('bad_operand'); + } + break; + default: + if (that.first.arity === 'prefix' || + that.first.arity === 'function') { + that.warn('unexpected_a'); + } + } + return that; + }; + return x; + } + + + function type(s, t, nud) { + var x = symbol(s); + x.arity = t; + if (nud) { + x.nud = nud; + } + return x; + } + + + function reserve(s, f) { + var x = symbol(s); + x.identifier = x.reserved = true; + if (typeof f === 'function') { + x.nud = f; + } + return x; + } + + + function constant(name) { + var x = reserve(name); + x.string = name; + x.nud = return_this; + return x; + } + + + function reservevar(s, v) { + return reserve(s, function () { + if (typeof v === 'function') { + v(this); + } + return this; + }); + } + + + function infix(s, p, f, w) { + var x = symbol(s, p); + reserve_name(x); + x.led = function (left) { + this.arity = 'infix'; + if (!w) { + spaces(prev_token, token); + spaces(); + } + if (!option.bitwise && this.bitwise) { + this.warn('unexpected_a'); + } + if (typeof f === 'function') { + return f(left, this); + } + this.first = left; + this.second = expression(p); + return this; + }; + return x; + } + + function expected_relation(node, message) { + if (node.assign) { + node.warn(message || 'conditional_assignment'); + } + return node; + } + + function expected_condition(node, message) { + switch (node.id) { + case '[': + case '-': + if (node.arity !== 'infix') { + node.warn(message || 'weird_condition'); + } + break; + case 'false': + case 'function': + case 'Infinity': + case 'NaN': + case 'null': + case 'true': + case 'undefined': + case 'void': + case '(number)': + case '(regexp)': + case '(string)': + case '{': + case '?': + case '~': + node.warn(message || 'weird_condition'); + break; + case '(': + if (node.first.id === 'new' || + (node.first.string === 'Boolean') || + (node.first.id === '.' && + numbery[node.first.second.string] === true)) { + node.warn(message || 'weird_condition'); + } + break; + } + return node; + } + + function check_relation(node) { + switch (node.arity) { + case 'prefix': + switch (node.id) { + case '{': + case '[': + node.warn('unexpected_a'); + break; + case '!': + node.warn('confusing_a'); + break; + } + break; + case 'function': + case 'regexp': + node.warn('unexpected_a'); + break; + default: + if (node.id === 'NaN') { + node.warn('isNaN'); + } else if (node.relation) { + node.warn('weird_relation'); + } + } + return node; + } + + + function relation(s, eqeq) { + var x = infix(s, 100, function (left, that) { + check_relation(left); + if (eqeq && !option.eqeq) { + that.warn('expected_a_b', eqeq, that.id); + } + var right = expression(100); + if (are_similar(left, right) || + ((left.id === '(string)' || left.id === '(number)') && + (right.id === '(string)' || right.id === '(number)'))) { + that.warn('weird_relation'); + } else if (left.id === 'typeof') { + if (right.id !== '(string)') { + right.warn("expected_string_a", artifact(right)); + } else if (right.string === 'undefined' || + right.string === 'null') { + left.warn("unexpected_typeof_a", right.string); + } + } else if (right.id === 'typeof') { + if (left.id !== '(string)') { + left.warn("expected_string_a", artifact(left)); + } else if (left.string === 'undefined' || + left.string === 'null') { + right.warn("unexpected_typeof_a", left.string); + } + } + that.first = left; + that.second = check_relation(right); + return that; + }); + x.relation = true; + return x; + } + + function lvalue(that, s) { + var master; + if (that.identifier) { + master = scope[that.string]; + if (master) { + if (scope[that.string].writeable !== true) { + that.warn('read_only'); + } + master.used -= 1; + if (s === '=') { + master.init = true; + } + } + } else if (that.id === '.' || that.id === '[') { + if (!that.first || that.first.string === 'arguments') { + that.warn('bad_assignment'); + } + } else { + that.warn('bad_assignment'); + } + } + + + function assignop(s, op) { + var x = infix(s, 20, function (left, that) { + var next; + that.first = left; + lvalue(left, s); + that.second = expression(20); + if (that.id === '=' && are_similar(that.first, that.second)) { + that.warn('weird_assignment'); + } + next = that; + while (next_token.id === '=') { + lvalue(next.second, '='); + next_token.first = next.second; + next.second = next_token; + next = next_token; + advance('='); + next.second = expression(20); + } + return that; + }); + x.assign = true; + if (op) { + if (syntax[op].bitwise) { + x.bitwise = true; + } + } + return x; + } + + + function bitwise(s, p) { + var x = infix(s, p, 'number'); + x.bitwise = true; + return x; + } + + + function suffix(s) { + var x = symbol(s, 150); + x.led = function (left) { + no_space_only(prev_token, token); + if (!option.plusplus) { + this.warn('unexpected_a'); + } else if ((!left.identifier || left.reserved) && + left.id !== '.' && left.id !== '[') { + this.warn('bad_operand'); + } + this.first = left; + this.arity = 'suffix'; + return this; + }; + return x; + } + + + function optional_identifier(variable) { + if (next_token.identifier) { + advance(); + if (token.reserved && variable) { + token.warn('expected_identifier_a_reserved'); + } + return token.string; + } + } + + + function identifier(variable) { + var i = optional_identifier(variable); + if (!i) { + next_token.stop(token.id === 'function' && next_token.id === '(' + ? 'name_function' + : 'expected_identifier_a'); + } + return i; + } + + + function statement() { + + var label, preamble, the_statement; + +// We don't like the empty statement. + + if (next_token.id === ';') { + next_token.warn('unexpected_a'); + semicolon(); + return; + } + +// Is this a labeled statement? + + if (next_token.identifier && !next_token.reserved && peek().id === ':') { + edge('label'); + label = next_token; + advance(); + advance(':'); + define('label', label); + if (next_token.labeled !== true || funct === global_funct) { + label.stop('unexpected_label_a'); + } else if (jx.test(label.string + ':')) { + label.warn('url'); + } + next_token.label = label; + label.init = true; + label.statement = next_token; + } + +// Parse the statement. + + preamble = next_token; + if (token.id !== 'else') { + edge(); + } + step_in('statement'); + the_statement = expression(0, true); + if (the_statement) { + +// Look for the final semicolon. + + if (the_statement.arity === 'statement') { + if (the_statement.id === 'switch' || + (the_statement.block && the_statement.id !== 'do')) { + spaces(); + } else { + semicolon(); + } + } else { + +// If this is an expression statement, determine if it is acceptable. +// We do not like +// new Blah; +// statements. If it is to be used at all, new should only be used to make +// objects, not side effects. The expression statements we do like do +// assignment or invocation or delete. + + if (the_statement.id === '(') { + if (the_statement.first.id === 'new') { + next_token.warn('bad_new'); + } + } else if (the_statement.id === '++' || + the_statement.id === '--') { + lvalue(the_statement.first); + } else if (!the_statement.assign && + the_statement.id !== 'delete') { + if (!option.closure || !preamble.comments) { + preamble.warn('assignment_function_expression'); + } + } + semicolon(); + } + } + step_out(); + if (label) { + label.dead = true; + } + return the_statement; + } + + + function statements() { + var array = [], disruptor, the_statement; + +// A disrupt statement may not be followed by any other statement. +// If the last statement is disrupt, then the sequence is disrupt. + + while (next_token.postscript !== true) { + if (next_token.id === ';') { + next_token.warn('unexpected_a'); + semicolon(); + } else { + if (next_token.string === 'use strict') { + if ((!node_js) || funct !== global_funct || array.length > 0) { + next_token.warn('function_strict'); + } + use_strict(); + } + if (disruptor) { + next_token.warn('unreachable_a_b', next_token.string, + disruptor.string); + disruptor = null; + } + the_statement = statement(); + if (the_statement) { + array.push(the_statement); + if (the_statement.disrupt) { + disruptor = the_statement; + array.disrupt = true; + } + } + } + } + return array; + } + + + function block(kind) { + +// A block is a sequence of statements wrapped in braces. + + var array, + curly = next_token, + old_block_var = block_var, + old_in_block = in_block, + old_strict_mode = strict_mode; + + in_block = kind !== 'function' && kind !== 'try' && kind !== 'catch'; + block_var = []; + if (curly.id === '{') { + spaces(); + advance('{'); + step_in(); + if (kind === 'function' && !use_strict() && !old_strict_mode && + !option.sloppy && funct.level === 1) { + next_token.warn('missing_use_strict'); + } + array = statements(); + strict_mode = old_strict_mode; + step_out('}', curly); + } else if (in_block) { + curly.stop('expected_a_b', '{', artifact()); + } else { + curly.warn('expected_a_b', '{', artifact()); + array = [statement()]; + array.disrupt = array[0].disrupt; + } + if (kind !== 'catch' && array.length === 0 && !option.debug) { + curly.warn('empty_block'); + } + block_var.forEach(function (name) { + scope[name].dead = true; + }); + block_var = old_block_var; + in_block = old_in_block; + return array; + } + + + function tally_property(name) { + if (option.properties && typeof property[name] !== 'number') { + token.warn('unexpected_property_a', name); + } + if (property[name]) { + property[name] += 1; + } else { + property[name] = 1; + } + } + + +// ECMAScript parser + + (function () { + var x = symbol('(identifier)'); + x.nud = function () { + var name = this.string, + master = scope[name], + writeable; + +// If the master is not in scope, then we may have an undeclared variable. +// Check the predefined list. If it was predefined, create the global +// variable. + + if (!master) { + writeable = predefined[name]; + if (typeof writeable === 'boolean') { + global_scope[name] = master = { + dead: false, + function: global_funct, + kind: 'var', + string: name, + writeable: writeable + }; + +// But if the variable is not in scope, and is not predefined, and if we are not +// in the global scope, then we have an undefined variable error. + + } else { + token.warn('used_before_a'); + } + } else { + this.master = master; + } + +// Annotate uses that cross scope boundaries. + + if (master) { + if (master.kind === 'label') { + this.warn('a_label'); + } else { + if (master.dead === true || master.dead === funct) { + this.warn('a_scope'); + } + master.used += 1; + if (master.function !== funct) { + if (master.function === global_funct) { + funct.global.push(name); + } else { + master.function.closure.push(name); + funct.outer.push(name); + } + } + } + } + return this; + }; + x.identifier = true; + }()); + + +// Build the syntax table by declaring the syntactic elements. + + type('(array)', 'array'); + type('(function)', 'function'); + type('(number)', 'number', return_this); + type('(object)', 'object'); + type('(string)', 'string', return_this); + type('(boolean)', 'boolean', return_this); + type('(regexp)', 'regexp', return_this); + + ultimate('(begin)'); + ultimate('(end)'); + ultimate('(error)'); + postscript(symbol('}')); + symbol(')'); + symbol(']'); + postscript(symbol('"')); + postscript(symbol('\'')); + symbol(';'); + symbol(':'); + symbol(','); + symbol('#'); + symbol('@'); + symbol('*/'); + postscript(reserve('case')); + reserve('catch'); + postscript(reserve('default')); + reserve('else'); + reserve('finally'); + + reservevar('arguments', function (x) { + if (strict_mode && funct === global_funct) { + x.warn('strict'); + } + funct.arguments = true; + }); + reservevar('eval'); + constant('false', 'boolean'); + constant('Infinity', 'number'); + constant('NaN', 'number'); + constant('null', ''); + reservevar('this', function (x) { + if (strict_mode && funct.statement && funct.name.charAt(0) > 'Z') { + x.warn('strict'); + } + }); + constant('true', 'boolean'); + constant('undefined', ''); + + infix('?', 30, function (left, that) { + step_in('?'); + that.first = expected_condition(expected_relation(left)); + that.second = expression(0); + spaces(); + step_out(); + var colon = next_token; + advance(':'); + step_in(':'); + spaces(); + that.third = expression(10); + that.arity = 'ternary'; + if (are_similar(that.second, that.third)) { + colon.warn('weird_ternary'); + } else if (are_similar(that.first, that.second)) { + that.warn('use_or'); + } + step_out(); + return that; + }); + + infix('||', 40, function (left, that) { + function paren_check(that) { + if (that.id === '&&' && !that.paren) { + that.warn('and'); + } + return that; + } + + that.first = paren_check(expected_condition(expected_relation(left))); + that.second = paren_check(expected_relation(expression(40))); + if (are_similar(that.first, that.second)) { + that.warn('weird_condition'); + } + return that; + }); + + infix('&&', 50, function (left, that) { + that.first = expected_condition(expected_relation(left)); + that.second = expected_relation(expression(50)); + if (are_similar(that.first, that.second)) { + that.warn('weird_condition'); + } + return that; + }); + + prefix('void', function (that) { + that.first = expression(0); + that.warn('expected_a_b', 'undefined', 'void'); + return that; + }); + + bitwise('|', 70); + bitwise('^', 80); + bitwise('&', 90); + + relation('==', '==='); + relation('==='); + relation('!=', '!=='); + relation('!=='); + relation('<'); + relation('>'); + relation('<='); + relation('>='); + + bitwise('<<', 120); + bitwise('>>', 120); + bitwise('>>>', 120); + + infix('in', 120, function (left, that) { + that.warn('infix_in'); + that.left = left; + that.right = expression(130); + return that; + }); + infix('instanceof', 120); + infix('+', 130, function (left, that) { + if (left.id === '(number)') { + if (left.number === 0) { + left.warn('unexpected_a', '0'); + } + } else if (left.id === '(string)') { + if (left.string === '') { + left.warn('expected_a_b', 'String', '\'\''); + } + } + var right = expression(130); + if (right.id === '(number)') { + if (right.number === 0) { + right.warn('unexpected_a', '0'); + } + } else if (right.id === '(string)') { + if (right.string === '') { + right.warn('expected_a_b', 'String', '\'\''); + } + } + if (left.id === right.id) { + if (left.id === '(string)' || left.id === '(number)') { + if (left.id === '(string)') { + left.string += right.string; + if (jx.test(left.string)) { + left.warn('url'); + } + } else { + left.number += right.number; + } + left.thru = right.thru; + return left; + } + } + that.first = left; + that.second = right; + return that; + }); + prefix('+'); + prefix('+++', function () { + token.warn('confusing_a'); + this.first = expression(150); + this.arity = 'prefix'; + return this; + }); + infix('+++', 130, function (left) { + token.warn('confusing_a'); + this.first = left; + this.second = expression(130); + return this; + }); + infix('-', 130, function (left, that) { + if ((left.id === '(number)' && left.number === 0) || left.id === '(string)') { + left.warn('unexpected_a'); + } + var right = expression(130); + if ((right.id === '(number)' && right.number === 0) || right.id === '(string)') { + right.warn('unexpected_a'); + } + if (left.id === right.id && left.id === '(number)') { + left.number -= right.number; + left.thru = right.thru; + return left; + } + that.first = left; + that.second = right; + return that; + }); + prefix('-'); + prefix('---', function () { + token.warn('confusing_a'); + this.first = expression(150); + this.arity = 'prefix'; + return this; + }); + infix('---', 130, function (left) { + token.warn('confusing_a'); + this.first = left; + this.second = expression(130); + return this; + }); + infix('*', 140, function (left, that) { + if ((left.id === '(number)' && (left.number === 0 || left.number === 1)) || left.id === '(string)') { + left.warn('unexpected_a'); + } + var right = expression(140); + if ((right.id === '(number)' && (right.number === 0 || right.number === 1)) || right.id === '(string)') { + right.warn('unexpected_a'); + } + if (left.id === right.id && left.id === '(number)') { + left.number *= right.number; + left.thru = right.thru; + return left; + } + that.first = left; + that.second = right; + return that; + }); + infix('/', 140, function (left, that) { + if ((left.id === '(number)' && left.number === 0) || left.id === '(string)') { + left.warn('unexpected_a'); + } + var right = expression(140); + if ((right.id === '(number)' && (right.number === 0 || right.number === 1)) || right.id === '(string)') { + right.warn('unexpected_a'); + } + if (left.id === right.id && left.id === '(number)') { + left.number /= right.number; + left.thru = right.thru; + return left; + } + that.first = left; + that.second = right; + return that; + }); + infix('%', 140, function (left, that) { + if ((left.id === '(number)' && (left.number === 0 || left.number === 1)) || left.id === '(string)') { + left.warn('unexpected_a'); + } + var right = expression(140); + if ((right.id === '(number)' && right.number === 0) || right.id === '(string)') { + right.warn('unexpected_a'); + } + if (left.id === right.id && left.id === '(number)') { + left.number %= right.number; + left.thru = right.thru; + return left; + } + that.first = left; + that.second = right; + return that; + }); + + suffix('++'); + prefix('++'); + + suffix('--'); + prefix('--'); + prefix('delete', function (that) { + one_space(); + var p = expression(0); + if (!p || (p.id !== '.' && p.id !== '[')) { + next_token.warn('deleted'); + } + that.first = p; + return that; + }); + + + prefix('~', function (that) { + no_space_only(); + if (!option.bitwise) { + that.warn('unexpected_a'); + } + that.first = expression(150); + return that; + }); + function banger(that) { + no_space_only(); + that.first = expected_condition(expression(150)); + if (bang[that.first.id] === that || that.first.assign) { + that.warn('confusing_a'); + } + return that; + } + prefix('!', banger); + prefix('!!', banger); + prefix('typeof'); + prefix('new', function (that) { + one_space(); + var c = expression(160), n, p, v; + that.first = c; + if (c.id !== 'function') { + if (c.identifier) { + switch (c.string) { + case 'Object': + token.warn('use_object'); + break; + case 'Array': + if (next_token.id === '(') { + p = next_token; + p.first = this; + advance('('); + if (next_token.id !== ')') { + n = expression(0); + p.second = [n]; + if (n.id === '(string)' || next_token.id === ',') { + p.warn('use_array'); + } + while (next_token.id === ',') { + advance(','); + p.second.push(expression(0)); + } + } else { + token.warn('use_array'); + } + advance(')', p); + return p; + } + token.warn('use_array'); + break; + case 'Number': + case 'String': + case 'Boolean': + case 'Math': + case 'JSON': + c.warn('not_a_constructor'); + break; + case 'Function': + if (!option.evil) { + next_token.warn('function_eval'); + } + break; + case 'Date': + case 'RegExp': + case 'this': + break; + default: + if (c.id !== 'function') { + v = c.string.charAt(0); + if (!option.newcap && (v < 'A' || v > 'Z')) { + token.warn('constructor_name_a'); + } + } + } + } else { + if (c.id !== '.' && c.id !== '[' && c.id !== '(') { + token.warn('bad_constructor'); + } + } + } else { + that.warn('weird_new'); + } + if (next_token.id !== '(') { + next_token.warn('missing_a', '()'); + } + return that; + }); + + infix('(', 160, function (left, that) { + var e, p; + if (indent && indent.mode === 'expression') { + no_space(prev_token, token); + } else { + no_space_only(prev_token, token); + } + if (!left.immed && left.id === 'function') { + next_token.warn('wrap_immediate'); + } + p = []; + if (left.identifier) { + if (left.string.match(/^[A-Z]([A-Z0-9_$]*[a-z][A-Za-z0-9_$]*)?$/)) { + if (left.string !== 'Number' && left.string !== 'String' && + left.string !== 'Boolean' && left.string !== 'Date') { + if (left.string === 'Math') { + left.warn('not_a_function'); + } else if (left.string === 'Object') { + token.warn('use_object'); + } else if (left.string === 'Array' || !option.newcap) { + left.warn('missing_a', 'new'); + } + } + } else if (left.string === 'JSON') { + left.warn('not_a_function'); + } + } else if (left.id === '.') { + if (left.second.string === 'split' && + left.first.id === '(string)') { + left.second.warn('use_array'); + } + } + step_in(); + if (next_token.id !== ')') { + no_space(); + for (;;) { + edge(); + e = expression(10); + if (left.string === 'Boolean' && (e.id === '!' || e.id === '~')) { + e.warn('weird_condition'); + } + p.push(e); + if (next_token.id !== ',') { + break; + } + comma(); + } + } + no_space(); + step_out(')', that); + if (typeof left === 'object') { + if (left.string === 'parseInt' && p.length === 1) { + left.warn('radix'); + } else if (left.string === 'String' && p.length >= 1 && p[0].id === '(string)') { + left.warn('unexpected_a'); + } + if (!option.evil) { + if (left.string === 'eval' || left.string === 'Function' || + left.string === 'execScript') { + left.warn('evil'); + } else if (p[0] && p[0].id === '(string)' && + (left.string === 'setTimeout' || + left.string === 'setInterval')) { + left.warn('implied_evil'); + } + } + if (!left.identifier && left.id !== '.' && left.id !== '[' && + left.id !== '(' && left.id !== '&&' && left.id !== '||' && + left.id !== '?') { + left.warn('bad_invocation'); + } + if (left.id === '.') { + if (p.length > 0 && + left.first && left.first.first && + are_similar(p[0], left.first.first)) { + if (left.second.string === 'call' || + (left.second.string === 'apply' && (p.length === 1 || + (p[1].arity === 'prefix' && p[1].id === '[')))) { + left.second.warn('unexpected_a'); + } + } + if (left.second.string === 'toString') { + if (left.first.id === '(string)' || left.first.id === '(number)') { + left.second.warn('unexpected_a'); + } + } + } + } + that.first = left; + that.second = p; + return that; + }, true); + + prefix('(', function (that) { + step_in('expression'); + no_space(); + edge(); + if (next_token.id === 'function') { + next_token.immed = true; + } + var value = expression(0); + value.paren = true; + no_space(); + step_out(')', that); + if (value.id === 'function') { + switch (next_token.id) { + case '(': + next_token.warn('move_invocation'); + break; + case '.': + case '[': + next_token.warn('unexpected_a'); + break; + default: + that.warn('bad_wrap'); + } + } else if (!value.arity) { + if (!option.closure || !that.comments) { + that.warn('unexpected_a'); + } + } + return value; + }); + + infix('.', 170, function (left, that) { + no_space(prev_token, token); + no_space(); + var name = identifier(); + if (typeof name === 'string') { + tally_property(name); + } + that.first = left; + that.second = token; + if (left && left.string === 'arguments' && + (name === 'callee' || name === 'caller')) { + left.warn('avoid_a', 'arguments.' + name); + } else if (!option.evil && left && left.string === 'document' && + (name === 'write' || name === 'writeln')) { + left.warn('write_is_wrong'); + } else if (!option.stupid && syx.test(name)) { + token.warn('sync_a'); + } + if (!option.evil && (name === 'eval' || name === 'execScript')) { + next_token.warn('evil'); + } + return that; + }, true); + + infix('[', 170, function (left, that) { + var e, s; + no_space_only(prev_token, token); + no_space(); + step_in(); + edge(); + e = expression(0); + switch (e.id) { + case '(number)': + if (e.id === '(number)' && left.id === 'arguments') { + left.warn('use_param'); + } + break; + case '(string)': + if (!option.evil && + (e.string === 'eval' || e.string === 'execScript')) { + e.warn('evil'); + } else if (!option.sub && ix.test(e.string)) { + s = syntax[e.string]; + if (!s || !s.reserved) { + e.warn('subscript'); + } + } + tally_property(e.string); + break; + } + step_out(']', that); + no_space(prev_token, token); + that.first = left; + that.second = e; + return that; + }, true); + + prefix('[', function (that) { + that.first = []; + step_in('array'); + while (next_token.id !== '(end)') { + while (next_token.id === ',') { + next_token.warn('unexpected_a'); + advance(','); + } + if (next_token.id === ']') { + break; + } + indent.wrap = false; + edge(); + that.first.push(expression(10)); + if (next_token.id === ',') { + comma(); + if (next_token.id === ']') { + token.warn('unexpected_a'); + break; + } + } else { + break; + } + } + step_out(']', that); + return that; + }, 170); + + + function property_name() { + var id = optional_identifier(); + if (!id) { + if (next_token.id === '(string)') { + id = next_token.string; + advance(); + } else if (next_token.id === '(number)') { + id = next_token.number.toString(); + advance(); + } + } + return id; + } + + + + assignop('='); + assignop('+=', '+'); + assignop('-=', '-'); + assignop('*=', '*'); + assignop('/=', '/').nud = function () { + next_token.stop('slash_equal'); + }; + assignop('%=', '%'); + assignop('&=', '&'); + assignop('|=', '|'); + assignop('^=', '^'); + assignop('<<=', '<<'); + assignop('>>=', '>>'); + assignop('>>>=', '>>>'); + + function function_parameters() { + var id, parameters = [], paren = next_token; + advance('('); + token.function = funct; + step_in(); + no_space(); + if (next_token.id !== ')') { + for (;;) { + edge(); + id = identifier(); + define('parameter', token); + parameters.push(id); + token.init = true; + token.writeable = true; + if (next_token.id !== ',') { + break; + } + comma(); + } + } + no_space(); + step_out(')', paren); + return parameters; + } + + function do_function(func, name) { + var old_funct = funct, + old_option = option, + old_scope = scope; + scope = Object.create(old_scope); + funct = { + closure: [], + global: [], + level: old_funct.level + 1, + line: next_token.line, + loopage: 0, + name: name || '\'' + (anonname || '').replace(nx, sanitize) + '\'', + outer: [], + scope: scope + }; + funct.parameter = function_parameters(); + func.function = funct; + option = Object.create(old_option); + functions.push(funct); + if (name) { + func.name = name; + func.string = name; + define('function', func); + func.init = true; + func.used += 1; + } + func.writeable = false; + one_space(); + func.block = block('function'); + Object.keys(scope).forEach(function (name) { + var master = scope[name]; + if (!master.used && master.kind !== 'exception' && + (master.kind !== 'parameter' || !option.unparam)) { + master.warn('unused_a'); + } else if (!master.init) { + master.warn('uninitialized_a'); + } + }); + funct = old_funct; + option = old_option; + scope = old_scope; + } + + prefix('{', function (that) { + var get, i, j, name, set, seen = Object.create(null); + that.first = []; + step_in(); + while (next_token.id !== '}') { + indent.wrap = false; + +// JSLint recognizes the ES5 extension for get/set in object literals, +// but requires that they be used in pairs. + + edge(); + if (next_token.string === 'get' && peek().id !== ':') { + get = next_token; + advance('get'); + one_space_only(); + name = next_token; + i = property_name(); + if (!i) { + next_token.stop('missing_property'); + } + get.string = ''; + do_function(get); + if (funct.loopage) { + get.warn('function_loop'); + } + if (get.function.parameter.length) { + get.warn('parameter_a_get_b', get.function.parameter[0], i); + } + comma(); + set = next_token; + spaces(); + edge(); + advance('set'); + set.string = ''; + one_space_only(); + j = property_name(); + if (i !== j) { + token.stop('expected_a_b', i, j || next_token.string); + } + do_function(set); + if (set.block.length === 0) { + token.warn('missing_a', 'throw'); + } + if (set.function.parameter.length === 0) { + set.stop('parameter_set_a', 'value'); + } else if (set.function.parameter[0] !== 'value') { + set.stop('expected_a_b', 'value', + set.function.parameter[0]); + } + name.first = [get, set]; + } else { + name = next_token; + i = property_name(); + if (typeof i !== 'string') { + next_token.stop('missing_property'); + } + advance(':'); + spaces(); + name.first = expression(10); + } + that.first.push(name); + if (seen[i] === true) { + next_token.warn('duplicate_a', i); + } + seen[i] = true; + tally_property(i); + if (next_token.id !== ',') { + break; + } + for (;;) { + comma(); + if (next_token.id !== ',') { + break; + } + next_token.warn('unexpected_a'); + } + if (next_token.id === '}') { + token.warn('unexpected_a'); + } + } + step_out('}', that); + return that; + }); + + stmt('{', function () { + next_token.warn('statement_block'); + this.arity = 'statement'; + this.block = statements(); + this.disrupt = this.block.disrupt; + advance('}', this); + return this; + }); + + stmt('/*global', directive); + stmt('/*globals', directive); + stmt('/*jslint', directive); + stmt('/*member', directive); + stmt('/*members', directive); + stmt('/*property', directive); + stmt('/*properties', directive); + + stmt('var', function () { + +// JavaScript does not have block scope. It only has function scope. So, +// declaring a variable in a block can have unexpected consequences. + +// var.first will contain an array, the array containing name tokens +// and assignment tokens. + + var assign, id, name; + + if (funct.loopage) { + next_token.warn('var_loop'); + } else if (funct.varstatement && !option.vars) { + next_token.warn('combine_var'); + } + if (funct !== global_funct) { + funct.varstatement = true; + } + this.arity = 'statement'; + this.first = []; + step_in('var'); + for (;;) { + name = next_token; + id = identifier(true); + define('var', name); + name.dead = funct; + if (next_token.id === '=') { + if (funct === global_funct && !name.writeable) { + name.warn('read_only'); + } + assign = next_token; + assign.first = name; + spaces(); + advance('='); + spaces(); + if (next_token.id === 'undefined') { + token.warn('unnecessary_initialize', id); + } + if (peek(0).id === '=' && next_token.identifier) { + next_token.stop('var_a_not'); + } + assign.second = expression(0); + assign.arity = 'infix'; + name.init = true; + this.first.push(assign); + } else { + this.first.push(name); + } + name.dead = false; + name.writeable = true; + if (next_token.id !== ',') { + break; + } + comma(); + indent.wrap = false; + if (var_mode && next_token.line === token.line && + this.first.length === 1) { + var_mode = null; + indent.open = false; + indent.at -= option.indent; + } + spaces(); + edge(); + } + var_mode = null; + step_out(); + return this; + }); + + stmt('function', function () { + one_space(); + if (in_block) { + token.warn('function_block'); + } + var name = next_token, + id = identifier(true); + define('var', name); + if (!name.writeable) { + name.warn('read_only'); + } + name.init = true; + name.statement = true; + no_space(); + this.arity = 'statement'; + do_function(this, id); + if (next_token.id === '(' && next_token.line === token.line) { + next_token.stop('function_statement'); + } + return this; + }); + + prefix('function', function (that) { + var id = optional_identifier(true), name; + if (id) { + name = token; + no_space(); + } else { + id = ''; + one_space(); + } + do_function(that, id); + if (name) { + name.function = that.function; + } + if (funct.loopage) { + that.warn('function_loop'); + } + switch (next_token.id) { + case ';': + case '(': + case ')': + case ',': + case ']': + case '}': + case ':': + case '(end)': + break; + case '.': + if (peek().string !== 'bind' || peek(1).id !== '(') { + next_token.warn('unexpected_a'); + } + break; + default: + next_token.stop('unexpected_a'); + } + that.arity = 'function'; + return that; + }); + + stmt('if', function () { + var paren = next_token; + one_space(); + advance('('); + step_in('control'); + no_space(); + edge(); + this.arity = 'statement'; + this.first = expected_condition(expected_relation(expression(0))); + no_space(); + step_out(')', paren); + one_space(); + this.block = block('if'); + if (next_token.id === 'else') { + if (this.block.disrupt) { + next_token.warn(this.elif ? 'use_nested_if' : 'unnecessary_else'); + } + one_space(); + advance('else'); + one_space(); + if (next_token.id === 'if') { + next_token.elif = true; + this.else = statement(true); + } else { + this.else = block('else'); + } + if (this.else.disrupt && this.block.disrupt) { + this.disrupt = true; + } + } + return this; + }); + + stmt('try', function () { + +// try.first The catch variable +// try.second The catch clause +// try.third The finally clause +// try.block The try block + + var exception_variable, paren; + one_space(); + this.arity = 'statement'; + this.block = block('try'); + if (next_token.id === 'catch') { + one_space(); + advance('catch'); + one_space(); + paren = next_token; + advance('('); + step_in('control'); + no_space(); + edge(); + exception_variable = next_token; + this.first = identifier(); + define('exception', exception_variable); + exception_variable.init = true; + no_space(); + step_out(')', paren); + one_space(); + this.second = block('catch'); + if (this.second.length) { + if (this.first === 'ignore') { + exception_variable.warn('unexpected_a'); + } + } else { + if (this.first !== 'ignore') { + exception_variable.warn('expected_a_b', 'ignore', + exception_variable.string); + } + } + exception_variable.dead = true; + } + if (next_token.id === 'finally') { + one_space(); + advance('finally'); + one_space(); + this.third = block('finally'); + } else if (!this.second) { + next_token.stop('expected_a_b', 'catch', artifact()); + } + return this; + }); + + labeled_stmt('while', function () { + one_space(); + var paren = next_token; + funct.loopage += 1; + advance('('); + step_in('control'); + no_space(); + edge(); + this.arity = 'statement'; + this.first = expected_relation(expression(0)); + if (this.first.id !== 'true') { + expected_condition(this.first, 'unexpected_a'); + } + no_space(); + step_out(')', paren); + one_space(); + this.block = block('while'); + if (this.block.disrupt) { + prev_token.warn('strange_loop'); + } + funct.loopage -= 1; + return this; + }); + + reserve('with'); + + labeled_stmt('switch', function () { + +// switch.first the switch expression +// switch.second the array of cases. A case is 'case' or 'default' token: +// case.first the array of case expressions +// case.second the array of statements +// If all of the arrays of statements are disrupt, then the switch is disrupt. + + var cases = [], + old_in_block = in_block, + particular, + that = token, + the_case = next_token; + + function find_duplicate_case(value) { + if (are_similar(particular, value)) { + value.warn('duplicate_a'); + } + } + + one_space(); + advance('('); + no_space(); + step_in(); + this.arity = 'statement'; + this.first = expected_condition(expected_relation(expression(0))); + no_space(); + step_out(')', the_case); + one_space(); + advance('{'); + step_in(); + in_block = true; + this.second = []; + if (that.from !== next_token.from && !option.white) { + next_token.warn('expected_a_at_b_c', next_token.string, that.from, next_token.from); + } + while (next_token.id === 'case') { + the_case = next_token; + the_case.first = []; + the_case.arity = 'case'; + for (;;) { + spaces(); + edge('case'); + advance('case'); + one_space(); + particular = expression(0); + cases.forEach(find_duplicate_case); + cases.push(particular); + the_case.first.push(particular); + if (particular.id === 'NaN') { + particular.warn('unexpected_a'); + } + no_space_only(); + advance(':'); + if (next_token.id !== 'case') { + break; + } + } + spaces(); + the_case.second = statements(); + if (the_case.second && the_case.second.length > 0) { + if (!the_case.second[the_case.second.length - 1].disrupt) { + next_token.warn('missing_a_after_b', 'break', 'case'); + } + } else { + next_token.warn('empty_case'); + } + this.second.push(the_case); + } + if (this.second.length === 0) { + next_token.warn('missing_a', 'case'); + } + if (next_token.id === 'default') { + spaces(); + the_case = next_token; + the_case.arity = 'case'; + edge('case'); + advance('default'); + no_space_only(); + advance(':'); + spaces(); + the_case.second = statements(); + if (the_case.second && the_case.second.length > 0) { + this.disrupt = the_case.second[the_case.second.length - 1].disrupt; + } else { + the_case.warn('empty_case'); + } + this.second.push(the_case); + } + if (this.break) { + this.disrupt = false; + } + spaces(); + step_out('}', this); + in_block = old_in_block; + return this; + }); + + stmt('debugger', function () { + if (!option.debug) { + this.warn('unexpected_a'); + } + this.arity = 'statement'; + return this; + }); + + labeled_stmt('do', function () { + funct.loopage += 1; + one_space(); + this.arity = 'statement'; + this.block = block('do'); + if (this.block.disrupt) { + prev_token.warn('strange_loop'); + } + one_space(); + advance('while'); + var paren = next_token; + one_space(); + advance('('); + step_in(); + no_space(); + edge(); + this.first = expected_condition(expected_relation(expression(0)), 'unexpected_a'); + no_space(); + step_out(')', paren); + funct.loopage -= 1; + return this; + }); + + labeled_stmt('for', function () { + + var blok, filter, master, ok = false, paren = next_token, value; + this.arity = 'statement'; + funct.loopage += 1; + advance('('); + if (next_token.id === ';') { + no_space(); + advance(';'); + no_space(); + advance(';'); + no_space(); + advance(')'); + blok = block('for'); + } else { + step_in('control'); + spaces(this, paren); + no_space(); + if (next_token.id === 'var') { + next_token.stop('move_var'); + } + edge(); + if (peek(0).id === 'in') { + this.forin = true; + value = expression(1000); + master = value.master; + if (!master) { + value.stop('bad_in_a'); + } + if (master.kind !== 'var' || master.function !== funct || + !master.writeable || master.dead) { + value.warn('bad_in_a'); + } + master.init = true; + master.used -= 1; + this.first = value; + advance('in'); + this.second = expression(20); + step_out(')', paren); + blok = block('for'); + if (!option.forin) { + if (blok.length === 1 && typeof blok[0] === 'object') { + if (blok[0].id === 'if' && !blok[0].else) { + filter = blok[0].first; + while (filter.id === '&&') { + filter = filter.first; + } + switch (filter.id) { + case '===': + case '!==': + ok = filter.first.id === '[' + ? are_similar(filter.first.first, this.second) && + are_similar(filter.first.second, this.first) + : filter.first.id === 'typeof' && + filter.first.first.id === '[' && + are_similar(filter.first.first.first, this.second) && + are_similar(filter.first.first.second, this.first); + break; + case '(': + ok = filter.first.id === '.' && (( + are_similar(filter.first.first, this.second) && + filter.first.second.string === 'hasOwnProperty' && + are_similar(filter.second[0], this.first) + ) || ( + filter.first.first.id === '.' && + filter.first.first.first.first && + filter.first.first.first.first.string === 'Object' && + filter.first.first.first.id === '.' && + filter.first.first.first.second.string === 'prototype' && + filter.first.first.second.string === 'hasOwnProperty' && + filter.first.second.string === 'call' && + are_similar(filter.second[0], this.second) && + are_similar(filter.second[1], this.first) + )); + break; + } + } else if (blok[0].id === 'switch') { + ok = blok[0].id === 'switch' && + blok[0].first.id === 'typeof' && + blok[0].first.first.id === '[' && + are_similar(blok[0].first.first.first, this.second) && + are_similar(blok[0].first.first.second, this.first); + } + } + if (!ok) { + this.warn('for_if'); + } + } + } else { + edge(); + this.first = []; + for (;;) { + this.first.push(expression(0, 'for')); + if (next_token.id !== ',') { + break; + } + comma(); + } + semicolon(); + edge(); + this.second = expected_relation(expression(0)); + if (this.second.id !== 'true') { + expected_condition(this.second, 'unexpected_a'); + } + semicolon(token); + if (next_token.id === ';') { + next_token.stop('expected_a_b', ')', ';'); + } + this.third = []; + edge(); + for (;;) { + this.third.push(expression(0, 'for')); + if (next_token.id !== ',') { + break; + } + comma(); + } + no_space(); + step_out(')', paren); + one_space(); + blok = block('for'); + } + } + if (blok.disrupt) { + prev_token.warn('strange_loop'); + } + this.block = blok; + funct.loopage -= 1; + return this; + }); + + function optional_label(that) { + var label = next_token.string, + master; + that.arity = 'statement'; + if (!funct.breakage || (!option.continue && that.id === 'continue')) { + that.warn('unexpected_a'); + } else if (next_token.identifier && token.line === next_token.line) { + one_space_only(); + master = scope[label]; + if (!master || master.kind !== 'label') { + next_token.warn('not_a_label'); + } else if (master.dead || master.function !== funct) { + next_token.warn('not_a_scope'); + } else { + master.used += 1; + if (that.id === 'break') { + master.statement.break = true; + } + if (funct.breakage[funct.breakage.length - 1] === master.statement) { + next_token.warn('unexpected_a'); + } + } + that.first = next_token; + advance(); + } else { + if (that.id === 'break') { + funct.breakage[funct.breakage.length - 1].break = true; + } + } + return that; + + } + + disrupt_stmt('break', function () { + return optional_label(this); + }); + + disrupt_stmt('continue', function () { + return optional_label(this); + }); + + disrupt_stmt('return', function () { + if (funct === global_funct) { + this.warn('unexpected_a'); + } + this.arity = 'statement'; + if (next_token.id !== ';' && next_token.line === token.line) { + if (option.closure) { + spaces(); + } else { + one_space_only(); + } + if (next_token.id === '/' || next_token.id === '(regexp)') { + next_token.warn('wrap_regexp'); + } + this.first = expression(0); + if (this.first.assign) { + this.first.warn('unexpected_a'); + } + } + return this; + }); + + disrupt_stmt('throw', function () { + this.arity = 'statement'; + one_space_only(); + this.first = expression(20); + return this; + }); + + +// Superfluous reserved words + + reserve('class'); + reserve('const'); + reserve('enum'); + reserve('export'); + reserve('extends'); + reserve('import'); + reserve('super'); + +// Harmony reserved words + + reserve('implements'); + reserve('interface'); + reserve('let'); + reserve('package'); + reserve('private'); + reserve('protected'); + reserve('public'); + reserve('static'); + reserve('yield'); + + +// Parse JSON + + function json_value() { + + function json_object() { + var brace = next_token, object = Object.create(null); + advance('{'); + if (next_token.id !== '}') { + while (next_token.id !== '(end)') { + while (next_token.id === ',') { + next_token.warn('unexpected_a'); + advance(','); + } + if (next_token.id !== '(string)') { + next_token.warn('expected_string_a'); + } + if (object[next_token.string] === true) { + next_token.warn('duplicate_a'); + } else if (next_token.string === '__proto__') { + next_token.warn('dangling_a'); + } else { + object[next_token.string] = true; + } + advance(); + advance(':'); + json_value(); + if (next_token.id !== ',') { + break; + } + advance(','); + if (next_token.id === '}') { + token.warn('unexpected_a'); + break; + } + } + } + advance('}', brace); + } + + function json_array() { + var bracket = next_token; + advance('['); + if (next_token.id !== ']') { + while (next_token.id !== '(end)') { + while (next_token.id === ',') { + next_token.warn('unexpected_a'); + advance(','); + } + json_value(); + if (next_token.id !== ',') { + break; + } + advance(','); + if (next_token.id === ']') { + token.warn('unexpected_a'); + break; + } + } + } + advance(']', bracket); + } + + switch (next_token.id) { + case '{': + json_object(); + break; + case '[': + json_array(); + break; + case 'true': + case 'false': + case 'null': + case '(number)': + case '(string)': + advance(); + break; + case '-': + advance('-'); + no_space_only(); + advance('(number)'); + break; + default: + next_token.stop('unexpected_a'); + } + } + + +// The actual JSLINT function itself. + + itself = function JSLint(the_source, the_option) { + + var i, predef, tree; + itself.errors = []; + itself.tree = ''; + itself.properties = ''; + begin = prev_token = token = next_token = + Object.create(syntax['(begin)']); + tokens = []; + predefined = Object.create(null); + add_to_predefined(standard); + property = Object.create(null); + if (the_option) { + option = Object.create(the_option); + predef = option.predef; + if (predef) { + if (Array.isArray(predef)) { + for (i = 0; i < predef.length; i += 1) { + predefined[predef[i]] = true; + } + } else if (typeof predef === 'object') { + add_to_predefined(predef); + } + } + } else { + option = Object.create(null); + } + option.indent = +option.indent || 4; + option.maxerr = +option.maxerr || 50; + global_scope = scope = Object.create(null); + global_funct = funct = { + scope: scope, + loopage: 0, + level: 0 + }; + functions = [funct]; + block_var = []; + + comments = []; + comments_off = false; + in_block = false; + indent = null; + json_mode = false; + lookahead = []; + node_js = false; + prereg = true; + strict_mode = false; + var_mode = null; + warnings = 0; + lex.init(the_source); + + assume(); + + try { + advance(); + if (next_token.id === '(number)') { + next_token.stop('unexpected_a'); + } else { + switch (next_token.id) { + case '{': + case '[': + comments_off = true; + json_mode = true; + json_value(); + break; + default: + +// If the first token is a semicolon, ignore it. This is sometimes used when +// files are intended to be appended to files that may be sloppy. A sloppy +// file may be depending on semicolon insertion on its last line. + + step_in(1); + if (next_token.id === ';' && !node_js) { + semicolon(); + } + tree = statements(); + begin.first = tree; + itself.tree = begin; + if (tree.disrupt) { + prev_token.warn('weird_program'); + } + } + } + indent = null; + advance('(end)'); + itself.property = property; + } catch (e) { + if (e) { // ~~ + itself.errors.push({ + reason : e.message, + line : e.line || next_token.line, + character : e.character || next_token.from + }, null); + } + } + return itself.errors.length === 0; + }; + + function unique(array) { + array = array.sort(); + var i, length = 0, previous, value; + for (i = 0; i < array.length; i += 1) { + value = array[i]; + if (value !== previous) { + array[length] = value; + previous = value; + length += 1; + } + } + array.length = length; + return array; + } + +// Data summary. + + itself.data = function () { + var data = {functions: []}, + function_data, + i, + the_function, + the_scope; + data.errors = itself.errors; + data.json = json_mode; + data.global = unique(Object.keys(global_scope)); + + function selects(name) { + var kind = the_scope[name].kind; + switch (kind) { + case 'var': + case 'exception': + case 'label': + function_data[kind].push(name); + break; + } + } + + for (i = 1; i < functions.length; i += 1) { + the_function = functions[i]; + function_data = { + name: the_function.name, + line: the_function.line, + level: the_function.level, + parameter: the_function.parameter, + var: [], + exception: [], + closure: unique(the_function.closure), + outer: unique(the_function.outer), + global: unique(the_function.global), + label: [] + }; + the_scope = the_function.scope; + Object.keys(the_scope).forEach(selects); + function_data.var.sort(); + function_data.exception.sort(); + function_data.label.sort(); + data.functions.push(function_data); + } + data.tokens = tokens; + return data; + }; + + itself.error_report = function (data) { + var evidence, i, output = [], warning; + if (data.errors.length) { + if (data.json) { + output.push('JSON: bad.
'); + } + for (i = 0; i < data.errors.length; i += 1) { + warning = data.errors[i]; + if (warning) { + evidence = warning.evidence || ''; + output.push(''); + if (isFinite(warning.line)) { + output.push('
line ' + + String(warning.line) + + ' character ' + String(warning.character) + + '
'); + } + output.push(warning.reason.entityify() + '
'); + if (evidence) { + output.push('
' + evidence.entityify() + '
'); + } + } + } + } + return output.join(''); + }; + + + itself.report = function (data) { + var dl, i, j, names, output = [], the_function; + + function detail(h, array) { + var comma_needed = false; + if (array.length) { + output.push("
" + h + "
"); + array.forEach(function (item) { + output.push((comma_needed ? ', ' : '') + item); + comma_needed = true; + }); + output.push("
"); + } + } + + output.push('
'); + if (data.global.length) { + detail('global', data.global); + dl = true; + } else if (data.json) { + if (!data.errors.length) { + output.push("
JSON: good.
"); + } + } else { + output.push("
No new global variables introduced.
"); + } + if (dl) { + output.push("
"); + } else { + output[0] = ''; + } + + if (data.functions) { + for (i = 0; i < data.functions.length; i += 1) { + the_function = data.functions[i]; + names = []; + if (the_function.params) { + for (j = 0; j < the_function.params.length; j += 1) { + names[j] = the_function.params[j].string; + } + } + output.push('
line ' + String(the_function.line) + + '
' + the_function.name.entityify()); + detail('parameter', the_function.parameter); + detail('variable', the_function.var); + detail('exception', the_function.exception); + detail('closure', the_function.closure); + detail('outer', the_function.outer); + detail('global', the_function.global); + detail('label', the_function.label); + output.push('
'); + } + } + return output.join(''); + }; + + itself.properties_report = function (property) { + if (!property) { + return ''; + } + var i, + key, + keys = Object.keys(property).sort(), + mem = ' ', + name, + not_first = false, + output = ['/*properties']; + for (i = 0; i < keys.length; i += 1) { + key = keys[i]; + if (property[key] > 0) { + if (not_first) { + mem += ','; + } + name = ix.test(key) + ? key + : '\'' + key.replace(nx, sanitize) + '\''; + if (mem.length + name.length >= 80) { + output.push(mem); + mem = ' '; + } else { + mem += ' '; + } + mem += name; + not_first = true; + } + } + output.push(mem, '*/\n'); + return output.join('\n'); + }; + + itself.color = function (data) { + var from, + i = 1, + level, + line, + result = [], + thru, + data_token = data.tokens[0]; + while (data_token && data_token.id !== '(end)') { + from = data_token.from; + line = data_token.line; + thru = data_token.thru; + level = data_token.function.level; + do { + thru = data_token.thru; + data_token = data.tokens[i]; + i += 1; + } while (data_token && data_token.line === line && + data_token.from - thru < 5 && + level === data_token.function.level); + result.push({ + line: line, + level: level, + from: from, + thru: thru + }); + } + return result; + }; + + itself.jslint = itself; + + itself.edition = '2014-02-06'; + + return itself; +}()); diff --git a/js/common/modules/org/arangodb/statistics.js b/js/common/modules/org/arangodb/statistics.js index 973f575b85..beb72ac2f2 100644 --- a/js/common/modules/org/arangodb/statistics.js +++ b/js/common/modules/org/arangodb/statistics.js @@ -54,10 +54,10 @@ if (cluster.isCluster()) { // ----------------------------------------------------------------------------- //////////////////////////////////////////////////////////////////////////////// -/// @brief reloads the user authentication data +/// @brief creates a statistics entry //////////////////////////////////////////////////////////////////////////////// -exports.historian = function (param) { +exports.historian = function () { "use strict"; try { diff --git a/js/server/server.js b/js/server/server.js index f8c19b8ca7..6425d3479f 100644 --- a/js/server/server.js +++ b/js/server/server.js @@ -58,9 +58,7 @@ var Buffer = require("buffer").Buffer; name: "statistics-collector", offset: 1, period: 10, - module: "org/arangodb/statistics", - funcname: "historian", - parameter: "_statistics" + command: "require('org/arangodb/statistics').historian();" }); } }()); diff --git a/js/server/tests/shell-tasks.js b/js/server/tests/shell-tasks.js new file mode 100644 index 0000000000..ef8a9ee0aa --- /dev/null +++ b/js/server/tests/shell-tasks.js @@ -0,0 +1,433 @@ +//////////////////////////////////////////////////////////////////////////////// +/// @brief test the task manager +/// +/// @file +/// +/// DISCLAIMER +/// +/// Copyright 2010-2012 triagens GmbH, Cologne, Germany +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// +/// Copyright holder is triAGENS GmbH, Cologne, Germany +/// +/// @author Jan Steemann +/// @author Copyright 2012, triAGENS GmbH, Cologne, Germany +//////////////////////////////////////////////////////////////////////////////// + +var jsunity = require("jsunity"); + +var arangodb = require("org/arangodb"); +var internal = require("internal"); +var db = arangodb.db; + +//////////////////////////////////////////////////////////////////////////////// +/// @brief test suite +//////////////////////////////////////////////////////////////////////////////// + +function TaskSuite () { + var cn = "UnitTestsTasks"; + + var cleanTasks = function () { + internal.getTasks().forEach(function(task) { + if (task.id.match(/^UnitTest/) || task.name.match(/^UnitTest/)) { + internal.deleteTask(task); + } + }); + }; + + var getTasks = function () { + var sorter = function (l, r) { + if (l.id !== r.id) { + return (l.id < r.id ? -1 : 1); + } + return 0; + }; + + return internal.getTasks().filter(function (task) { + return task.name.match(/^UnitTest/); + }).sort(sorter); + }; + + return { + +//////////////////////////////////////////////////////////////////////////////// +/// @brief set up +//////////////////////////////////////////////////////////////////////////////// + + setUp : function () { + cleanTasks(); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief tear down +//////////////////////////////////////////////////////////////////////////////// + + tearDown : function () { + cleanTasks(); + + db._drop(cn); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create a task without an id +//////////////////////////////////////////////////////////////////////////////// + + testCreateTaskNoId : function () { + try { + internal.executeTask({ }); + fail(); + } + catch (err) { + assertEqual(internal.errors.ERROR_BAD_PARAMETER.code, err.errorNum); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create a task without a period +//////////////////////////////////////////////////////////////////////////////// + + testCreateTaskNoPeriod : function () { + try { + internal.executeTask({ id: "UnitTestsNoPeriod", command: "1+1;" }); + fail(); + } + catch (err) { + assertEqual(internal.errors.ERROR_BAD_PARAMETER.code, err.errorNum); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create a task with an invalid period +//////////////////////////////////////////////////////////////////////////////// + + testCreateTaskInvalidPeriod1 : function () { + try { + internal.executeTask({ id: "UnitTestsNoPeriod", period: -1, command: "1+1;" }); + fail(); + } + catch (err) { + assertEqual(internal.errors.ERROR_BAD_PARAMETER.code, err.errorNum); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create a task with an invalid period +//////////////////////////////////////////////////////////////////////////////// + + testCreateTaskInvalidPeriod2 : function () { + try { + internal.executeTask({ id: "UnitTestsNoPeriod", period: 0, command: "1+1;" }); + fail(); + } + catch (err) { + assertEqual(internal.errors.ERROR_BAD_PARAMETER.code, err.errorNum); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create a task without a command +//////////////////////////////////////////////////////////////////////////////// + + testCreateTaskNoCommand : function () { + try { + internal.executeTask({ id: "UnitTestsNoPeriod", period: 1 }); + fail(); + } + catch (err) { + assertEqual(internal.errors.ERROR_BAD_PARAMETER.code, err.errorNum); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create a task with an automatic id +//////////////////////////////////////////////////////////////////////////////// + + testCreateTaskAutomaticId : function () { + var task = internal.executeTask({ name: "UnitTests1", command: "1+1;", period: 1 }); + + assertMatch(/^\d+$/, task.id); + assertEqual("UnitTests1", task.name); + assertEqual("periodic", task.type); + assertEqual(1, task.period); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create a task with a duplicate id +//////////////////////////////////////////////////////////////////////////////// + + testCreateTaskDuplicateId : function () { + var task = internal.executeTask({ id: "UnitTests1", name: "UnitTests1", command: "1+1;", period: 1 }); + + assertEqual("UnitTests1", task.id); + assertEqual("UnitTests1", task.name); + assertEqual("periodic", task.type); + + assertEqual(1, task.period); + + try { + internal.executeTask({ id: "UnitTests1", name: "UnitTests1", command: "1+1;", period: 1 }); + fail(); + } + catch (err) { + assertEqual(internal.errors.ERROR_TASK_DUPLICATE_ID.code, err.errorNum); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief remove without an id +//////////////////////////////////////////////////////////////////////////////// + + testCreateTaskRemoveWithoutId : function () { + try { + internal.deleteTask(); + fail(); + } + catch (err) { + assertEqual(internal.errors.ERROR_BAD_PARAMETER.code, err.errorNum); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create a task and remove it +//////////////////////////////////////////////////////////////////////////////// + + testCreateTaskRemoveByTask : function () { + var task = internal.executeTask({ id: "UnitTests1", name: "UnitTests1", command: "1+1;", period: 1 }); + + assertEqual("UnitTests1", task.id); + assertEqual("UnitTests1", task.name); + assertEqual("periodic", task.type); + assertEqual(1, task.period); + + internal.deleteTask(task); + + try { + // deleting again should fail + internal.deleteTask(task); + fail(); + } + catch (err) { + assertEqual(internal.errors.ERROR_TASK_NOT_FOUND.code, err.errorNum); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create a task and remove it +//////////////////////////////////////////////////////////////////////////////// + + testCreateTaskRemoveById : function () { + var task = internal.executeTask({ id: "UnitTests1", name: "UnitTests1", command: "1+1;", period: 1 }); + + assertEqual("UnitTests1", task.id); + assertEqual("UnitTests1", task.name); + assertEqual("periodic", task.type); + assertEqual(1, task.period); + + internal.deleteTask(task.id); + + try { + // deleting again should fail + internal.deleteTask(task.id); + fail(); + } + catch (err) { + assertEqual(internal.errors.ERROR_TASK_NOT_FOUND.code, err.errorNum); + } + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief get list of tasks +//////////////////////////////////////////////////////////////////////////////// + + testGetTasks : function () { + var task1 = internal.executeTask({ + id: "UnitTests1", + name: "UnitTests1", + command: "1+1;", + period: 1 + }); + + var task2 = internal.executeTask({ + id: "UnitTests2", + name: "UnitTests2", + command: "2+2;", + period: 2 + }); + + var tasks = getTasks(); + + assertEqual(2, tasks.length); + assertEqual(task1.id, tasks[0].id); + assertEqual(task1.name, tasks[0].name); + assertEqual(task1.type, tasks[0].type); + assertEqual(task1.period, tasks[0].period); + assertEqual(task1.database, tasks[0].database); + + assertEqual(task2.id, tasks[1].id); + assertEqual(task2.name, tasks[1].name); + assertEqual(task2.type, tasks[1].type); + assertEqual(task2.period, tasks[1].period); + assertEqual(task2.database, tasks[1].database); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief get list of tasks pre and post task deletion +//////////////////////////////////////////////////////////////////////////////// + + testGetTasks : function () { + var task1 = internal.executeTask({ + id: "UnitTests1", + name: "UnitTests1", + command: "1+1;", + period: 1 + }); + + var tasks = getTasks(); + + assertEqual(1, tasks.length); + assertEqual(task1.id, tasks[0].id); + assertEqual(task1.name, tasks[0].name); + assertEqual(task1.type, tasks[0].type); + assertEqual(task1.period, tasks[0].period); + assertEqual(task1.database, tasks[0].database); + + var task2 = internal.executeTask({ + id: "UnitTests2", + name: "UnitTests2", + command: "2+2;", + period: 2 + }); + + tasks = getTasks(); + + assertEqual(2, tasks.length); + assertEqual(task1.id, tasks[0].id); + assertEqual(task1.name, tasks[0].name); + assertEqual(task1.type, tasks[0].type); + assertEqual(task1.period, tasks[0].period); + assertEqual(task1.database, tasks[0].database); + assertEqual(task2.id, tasks[1].id); + assertEqual(task2.name, tasks[1].name); + assertEqual(task2.type, tasks[1].type); + assertEqual(task2.period, tasks[1].period); + assertEqual(task2.database, tasks[1].database); + + internal.deleteTask(task1); + + tasks = getTasks(); + + assertEqual(1, tasks.length); + assertEqual(task2.id, tasks[0].id); + assertEqual(task2.name, tasks[0].name); + assertEqual(task2.type, tasks[0].type); + assertEqual(task2.period, tasks[0].period); + assertEqual(task2.database, tasks[0].database); + + internal.deleteTask(task2); + + tasks = getTasks(); + + assertEqual(0, tasks.length); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create a task and run it +//////////////////////////////////////////////////////////////////////////////// + + testCreateStringCommand : function () { + db._drop(cn); + db._create(cn); + + assertEqual(0, db[cn].count()); + + var command = "require('internal').db." + cn + ".save({ value: params });"; + + var task = internal.executeTask({ + id: "UnitTests1", + name: "UnitTests1", + command: command, + period: 1, + params: 23 + }); + + assertEqual("UnitTests1", task.id); + assertEqual("UnitTests1", task.name); + assertEqual("periodic", task.type); + assertEqual(1, task.period); + assertEqual("_system", task.database); + + internal.wait(5); + + internal.deleteTask(task); + + assertTrue(db[cn].count() > 0); + assertTrue(db[cn].byExample({ value: 23 }).count() > 0); + }, + +//////////////////////////////////////////////////////////////////////////////// +/// @brief create a task and run it +//////////////////////////////////////////////////////////////////////////////// + + testCreateFunctionCommand : function () { + db._drop(cn); + db._create(cn); + + assertEqual(0, db[cn].count()); + + var command = function (params) { + require('internal').db[params.cn].save({ value: params.val }); + }; + + var task = internal.executeTask({ + id: "UnitTests1", + name: "UnitTests1", + command: command, + period: 1, + params: { cn: cn, val: 42 } + }); + + assertEqual("UnitTests1", task.id); + assertEqual("UnitTests1", task.name); + assertEqual("periodic", task.type); + assertEqual(1, task.period); + assertEqual("_system", task.database); + + internal.wait(5); + + internal.deleteTask(task); + + assertTrue(db[cn].count() > 0); + assertTrue(db[cn].byExample({ value: 23 }).count() === 0); + assertTrue(db[cn].byExample({ value: 42 }).count() > 0); + } + + }; +} + +// ----------------------------------------------------------------------------- +// --SECTION-- main +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief executes the test suite +//////////////////////////////////////////////////////////////////////////////// + +jsunity.run(TaskSuite); + +return jsunity.done(); + +// Local Variables: +// mode: outline-minor +// outline-regexp: "^\\(/// @brief\\|/// @addtogroup\\|// --SECTION--\\|/// @page\\|/// @}\\)" +// End: diff --git a/lib/Scheduler/Scheduler.cpp b/lib/Scheduler/Scheduler.cpp index 1dd9c2cc6c..2867c32553 100644 --- a/lib/Scheduler/Scheduler.cpp +++ b/lib/Scheduler/Scheduler.cpp @@ -238,16 +238,11 @@ TRI_json_t* Scheduler::getUserTasks () { Task* task = (*i).first; if (task->isUserDefined()) { - TRI_json_t* obj = TRI_CreateArrayJson(TRI_UNKNOWN_MEM_ZONE); + TRI_json_t* obj = task->toJson(); if (obj != 0) { - TRI_Insert3ArrayJson(TRI_UNKNOWN_MEM_ZONE, obj, "id", TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, task->id().c_str())); - TRI_Insert3ArrayJson(TRI_UNKNOWN_MEM_ZONE, obj, "name", TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, task->name().c_str())); - - task->getDescription(obj); + TRI_PushBack3ListJson(TRI_UNKNOWN_MEM_ZONE, json, obj); } - - TRI_PushBack3ListJson(TRI_UNKNOWN_MEM_ZONE, json, obj); } ++i; diff --git a/lib/Scheduler/Task.cpp b/lib/Scheduler/Task.cpp index 692570a693..8994fd9cca 100644 --- a/lib/Scheduler/Task.cpp +++ b/lib/Scheduler/Task.cpp @@ -59,9 +59,26 @@ Task::~Task () { } // ----------------------------------------------------------------------------- -// protected methods +// public methods // ----------------------------------------------------------------------------- +//////////////////////////////////////////////////////////////////////////////// +/// @brief get a JSON representation of the task +//////////////////////////////////////////////////////////////////////////////// + +TRI_json_t* Task::toJson () { + TRI_json_t* json = TRI_CreateArrayJson(TRI_UNKNOWN_MEM_ZONE); + + if (json != 0) { + TRI_Insert3ArrayJson(TRI_UNKNOWN_MEM_ZONE, json, "id", TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, this->id().c_str())); + TRI_Insert3ArrayJson(TRI_UNKNOWN_MEM_ZONE, json, "name", TRI_CreateStringCopyJson(TRI_UNKNOWN_MEM_ZONE, this->name().c_str())); + + this->getDescription(json); + } + + return json; +} + //////////////////////////////////////////////////////////////////////////////// /// @brief whether or not the task is user-defined /// note: this function may be overridden @@ -71,14 +88,6 @@ bool Task::isUserDefined () const { return false; } -//////////////////////////////////////////////////////////////////////////////// -/// @brief get a task specific description in JSON format -/// this does nothing for basic tasks, but derived classes may override it -//////////////////////////////////////////////////////////////////////////////// - -void Task::getDescription (TRI_json_t* json) { -} - //////////////////////////////////////////////////////////////////////////////// /// @brief allow thread to run on slave event loop //////////////////////////////////////////////////////////////////////////////// @@ -87,3 +96,15 @@ bool Task::needsMainEventLoop () const { return false; } +// ----------------------------------------------------------------------------- +// protected methods +// ----------------------------------------------------------------------------- + +//////////////////////////////////////////////////////////////////////////////// +/// @brief get a task specific description in JSON format +/// this does nothing for basic tasks, but derived classes may override it +//////////////////////////////////////////////////////////////////////////////// + +void Task::getDescription (TRI_json_t* json) { +} + diff --git a/lib/Scheduler/Task.h b/lib/Scheduler/Task.h index b71d9e7465..1bd5c7c392 100644 --- a/lib/Scheduler/Task.h +++ b/lib/Scheduler/Task.h @@ -99,10 +99,10 @@ namespace triagens { } //////////////////////////////////////////////////////////////////////////////// -/// @brief get a task specific description in JSON format +/// @brief get a JSON representation of the task //////////////////////////////////////////////////////////////////////////////// - virtual void getDescription (struct TRI_json_s*); + struct TRI_json_s* toJson (); //////////////////////////////////////////////////////////////////////////////// /// @brief whether or not the task is a user task @@ -138,6 +138,12 @@ namespace triagens { protected: +//////////////////////////////////////////////////////////////////////////////// +/// @brief get a task specific description in JSON format +//////////////////////////////////////////////////////////////////////////////// + + virtual void getDescription (struct TRI_json_s*); + //////////////////////////////////////////////////////////////////////////////// /// @brief called to set up the callback information ///