planning
All checks were successful
Publish To Prod / deploy_and_publish (push) Successful in 35s

This commit is contained in:
2024-10-14 09:15:30 +02:00
parent bcba00a730
commit 6e64e138e2
21059 changed files with 2317811 additions and 1 deletions

View File

@@ -0,0 +1 @@
node_modules

View File

@@ -0,0 +1,17 @@
all: build test site
build:
./node_modules/jison/lib/cli.js src/jsonlint.y src/jsonlint.l
mv jsonlint.js lib/jsonlint.js
node scripts/bundle.js | ./node_modules/uglify-js/bin/uglifyjs > web/jsonlint.js
site:
cp web/jsonlint.js ../jsonlint-pages/jsonlint.js
deploy: site
cd ../jsonlint-pages && git commit -a -m 'deploy site updates' && git push origin gh-pages
test: lib/jsonlint.js test/all-tests.js
node test/all-tests.js

View File

@@ -0,0 +1,7 @@
JSON Lint
=========
A fork of the `lines-primitive` branch of [tmcw/jsonlint](https://github.com/tmcw/jsonlint), which is a fork of [zaach/jsonlint](https://github.com/zaach/jsonlint) that adds line number annotations to the parsed JSON.
This fork is used by Mapbox GL JS, specifically for providing helpful error messages when validating Mapbox GL style JSON documents.

View File

@@ -0,0 +1,92 @@
#!/usr/bin/env node
/**
* Manual formatter taken straight from https://github.com/umbrae/jsonlintdotcom
**/
/*jslint white: true, devel: true, onevar: true, browser: true, undef: true, nomen: true, regexp: true, plusplus: false, bitwise: true, newcap: true, maxerr: 50, indent: 4 */
/**
* jsl.format - Provide json reformatting in a character-by-character approach, so that even invalid JSON may be reformatted (to the best of its ability).
*
**/
var formatter = (function () {
function repeat(s, count) {
return new Array(count + 1).join(s);
}
function formatJson(json, indentChars) {
var i = 0,
il = 0,
tab = (typeof indentChars !== "undefined") ? indentChars : " ",
newJson = "",
indentLevel = 0,
inString = false,
currentChar = null;
for (i = 0, il = json.length; i < il; i += 1) {
currentChar = json.charAt(i);
switch (currentChar) {
case '{':
case '[':
if (!inString) {
newJson += currentChar + "\n" + repeat(tab, indentLevel + 1);
indentLevel += 1;
} else {
newJson += currentChar;
}
break;
case '}':
case ']':
if (!inString) {
indentLevel -= 1;
newJson += "\n" + repeat(tab, indentLevel) + currentChar;
} else {
newJson += currentChar;
}
break;
case ',':
if (!inString) {
newJson += ",\n" + repeat(tab, indentLevel);
} else {
newJson += currentChar;
}
break;
case ':':
if (!inString) {
newJson += ": ";
} else {
newJson += currentChar;
}
break;
case ' ':
case "\n":
case "\t":
if (inString) {
newJson += currentChar;
}
break;
case '"':
if (i > 0 && json.charAt(i - 1) !== '\\') {
inString = !inString;
}
newJson += currentChar;
break;
default:
newJson += currentChar;
break;
}
}
return newJson;
}
return { "formatJson": formatJson };
}());
if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
exports.formatter = formatter;
}

View File

@@ -0,0 +1,681 @@
/* parser generated by jison 0.4.15 */
/*
Returns a Parser object of the following structure:
Parser: {
yy: {}
}
Parser.prototype: {
yy: {},
trace: function(),
symbols_: {associative list: name ==> number},
terminals_: {associative list: number ==> name},
productions_: [...],
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
table: [...],
defaultActions: {...},
parseError: function(str, hash),
parse: function(input),
lexer: {
EOF: 1,
parseError: function(str, hash),
setInput: function(input),
input: function(),
unput: function(str),
more: function(),
less: function(n),
pastInput: function(),
upcomingInput: function(),
showPosition: function(),
test_match: function(regex_match_array, rule_index),
next: function(),
lex: function(),
begin: function(condition),
popState: function(),
_currentRules: function(),
topState: function(),
pushState: function(condition),
options: {
ranges: boolean (optional: true ==> token location info will include a .range[] member)
flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
},
performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
rules: [...],
conditions: {associative list: name ==> set},
}
}
token location info (@$, _$, etc.): {
first_line: n,
last_line: n,
first_column: n,
last_column: n,
range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based)
}
the parseError function receives a 'hash' object with these members for lexer and parser errors: {
text: (matched text)
token: (the produced terminal token, if any)
line: (yylineno)
}
while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
loc: (yylloc)
expected: (string describing the set of expected tokens)
recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
}
*/
var parser = (function(){
var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,12],$V1=[1,13],$V2=[1,9],$V3=[1,10],$V4=[1,11],$V5=[1,14],$V6=[1,15],$V7=[14,18,22,24],$V8=[18,22],$V9=[22,24];
var parser = {trace: function trace() { },
yy: {},
symbols_: {"error":2,"JSONString":3,"STRING":4,"JSONNumber":5,"NUMBER":6,"JSONNullLiteral":7,"NULL":8,"JSONBooleanLiteral":9,"TRUE":10,"FALSE":11,"JSONText":12,"JSONValue":13,"EOF":14,"JSONObject":15,"JSONArray":16,"{":17,"}":18,"JSONMemberList":19,"JSONMember":20,":":21,",":22,"[":23,"]":24,"JSONElementList":25,"$accept":0,"$end":1},
terminals_: {2:"error",4:"STRING",6:"NUMBER",8:"NULL",10:"TRUE",11:"FALSE",14:"EOF",17:"{",18:"}",21:":",22:",",23:"[",24:"]"},
productions_: [0,[3,1],[5,1],[7,1],[9,1],[9,1],[12,2],[13,1],[13,1],[13,1],[13,1],[13,1],[13,1],[15,2],[15,3],[20,3],[19,1],[19,3],[16,2],[16,3],[25,1],[25,3]],
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
/* this == yyval */
var $0 = $$.length - 1;
switch (yystate) {
case 1:
// replace escaped characters with actual character
this.$ = new String(yytext.replace(/\\(\\|")/g, "$"+"1")
.replace(/\\n/g,'\n')
.replace(/\\r/g,'\r')
.replace(/\\t/g,'\t')
.replace(/\\v/g,'\v')
.replace(/\\f/g,'\f')
.replace(/\\b/g,'\b'));
this.$.__line__ = this._$.first_line;
break;
case 2:
this.$ = new Number(yytext);
this.$.__line__ = this._$.first_line;
break;
case 3:
this.$ = null;
break;
case 4:
this.$ = new Boolean(true);
this.$.__line__ = this._$.first_line;
break;
case 5:
this.$ = new Boolean(false);
this.$.__line__ = this._$.first_line;
break;
case 6:
return this.$ = $$[$0-1];
break;
case 13:
this.$ = {}; Object.defineProperty(this.$, '__line__', {
value: this._$.first_line,
enumerable: false
})
break;
case 14: case 19:
this.$ = $$[$0-1]; Object.defineProperty(this.$, '__line__', {
value: this._$.first_line,
enumerable: false
})
break;
case 15:
this.$ = [$$[$0-2], $$[$0]];
break;
case 16:
this.$ = {}; this.$[$$[$0][0]] = $$[$0][1];
break;
case 17:
this.$ = $$[$0-2]; $$[$0-2][$$[$0][0]] = $$[$0][1];
break;
case 18:
this.$ = []; Object.defineProperty(this.$, '__line__', {
value: this._$.first_line,
enumerable: false
})
break;
case 20:
this.$ = [$$[$0]];
break;
case 21:
this.$ = $$[$0-2]; $$[$0-2].push($$[$0]);
break;
}
},
table: [{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,12:1,13:2,15:7,16:8,17:$V5,23:$V6},{1:[3]},{14:[1,16]},o($V7,[2,7]),o($V7,[2,8]),o($V7,[2,9]),o($V7,[2,10]),o($V7,[2,11]),o($V7,[2,12]),o($V7,[2,3]),o($V7,[2,4]),o($V7,[2,5]),o([14,18,21,22,24],[2,1]),o($V7,[2,2]),{3:20,4:$V0,18:[1,17],19:18,20:19},{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,13:23,15:7,16:8,17:$V5,23:$V6,24:[1,21],25:22},{1:[2,6]},o($V7,[2,13]),{18:[1,24],22:[1,25]},o($V8,[2,16]),{21:[1,26]},o($V7,[2,18]),{22:[1,28],24:[1,27]},o($V9,[2,20]),o($V7,[2,14]),{3:20,4:$V0,20:29},{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,13:30,15:7,16:8,17:$V5,23:$V6},o($V7,[2,19]),{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,13:31,15:7,16:8,17:$V5,23:$V6},o($V8,[2,17]),o($V8,[2,15]),o($V9,[2,21])],
defaultActions: {16:[2,6]},
parseError: function parseError(str, hash) {
if (hash.recoverable) {
this.trace(str);
} else {
throw new Error(str);
}
},
parse: function parse(input) {
var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1;
var args = lstack.slice.call(arguments, 1);
var lexer = Object.create(this.lexer);
var sharedState = { yy: {} };
for (var k in this.yy) {
if (Object.prototype.hasOwnProperty.call(this.yy, k)) {
sharedState.yy[k] = this.yy[k];
}
}
lexer.setInput(input, sharedState.yy);
sharedState.yy.lexer = lexer;
sharedState.yy.parser = this;
if (typeof lexer.yylloc == 'undefined') {
lexer.yylloc = {};
}
var yyloc = lexer.yylloc;
lstack.push(yyloc);
var ranges = lexer.options && lexer.options.ranges;
if (typeof sharedState.yy.parseError === 'function') {
this.parseError = sharedState.yy.parseError;
} else {
this.parseError = Object.getPrototypeOf(this).parseError;
}
function popStack(n) {
stack.length = stack.length - 2 * n;
vstack.length = vstack.length - n;
lstack.length = lstack.length - n;
}
_token_stack:
function lex() {
var token;
token = lexer.lex() || EOF;
if (typeof token !== 'number') {
token = self.symbols_[token] || token;
}
return token;
}
var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected;
while (true) {
state = stack[stack.length - 1];
if (this.defaultActions[state]) {
action = this.defaultActions[state];
} else {
if (symbol === null || typeof symbol == 'undefined') {
symbol = lex();
}
action = table[state] && table[state][symbol];
}
if (typeof action === 'undefined' || !action.length || !action[0]) {
var errStr = '';
expected = [];
for (p in table[state]) {
if (this.terminals_[p] && p > TERROR) {
expected.push('\'' + this.terminals_[p] + '\'');
}
}
if (lexer.showPosition) {
errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
} else {
errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
}
this.parseError(errStr, {
text: lexer.match,
token: this.terminals_[symbol] || symbol,
line: lexer.yylineno,
loc: yyloc,
expected: expected
});
}
if (action[0] instanceof Array && action.length > 1) {
throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
}
switch (action[0]) {
case 1:
stack.push(symbol);
vstack.push(lexer.yytext);
lstack.push(lexer.yylloc);
stack.push(action[1]);
symbol = null;
if (!preErrorSymbol) {
yyleng = lexer.yyleng;
yytext = lexer.yytext;
yylineno = lexer.yylineno;
yyloc = lexer.yylloc;
if (recovering > 0) {
recovering--;
}
} else {
symbol = preErrorSymbol;
preErrorSymbol = null;
}
break;
case 2:
len = this.productions_[action[1]][1];
yyval.$ = vstack[vstack.length - len];
yyval._$ = {
first_line: lstack[lstack.length - (len || 1)].first_line,
last_line: lstack[lstack.length - 1].last_line,
first_column: lstack[lstack.length - (len || 1)].first_column,
last_column: lstack[lstack.length - 1].last_column
};
if (ranges) {
yyval._$.range = [
lstack[lstack.length - (len || 1)].range[0],
lstack[lstack.length - 1].range[1]
];
}
r = this.performAction.apply(yyval, [
yytext,
yyleng,
yylineno,
sharedState.yy,
action[1],
vstack,
lstack
].concat(args));
if (typeof r !== 'undefined') {
return r;
}
if (len) {
stack = stack.slice(0, -1 * len * 2);
vstack = vstack.slice(0, -1 * len);
lstack = lstack.slice(0, -1 * len);
}
stack.push(this.productions_[action[1]][0]);
vstack.push(yyval.$);
lstack.push(yyval._$);
newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
stack.push(newState);
break;
case 3:
return true;
}
}
return true;
}};
/* generated by jison-lex 0.3.4 */
var lexer = (function(){
var lexer = ({
EOF:1,
parseError:function parseError(str, hash) {
if (this.yy.parser) {
this.yy.parser.parseError(str, hash);
} else {
throw new Error(str);
}
},
// resets the lexer, sets new input
setInput:function (input, yy) {
this.yy = yy || this.yy || {};
this._input = input;
this._more = this._backtrack = this.done = false;
this.yylineno = this.yyleng = 0;
this.yytext = this.matched = this.match = '';
this.conditionStack = ['INITIAL'];
this.yylloc = {
first_line: 1,
first_column: 0,
last_line: 1,
last_column: 0
};
if (this.options.ranges) {
this.yylloc.range = [0,0];
}
this.offset = 0;
return this;
},
// consumes and returns one char from the input
input:function () {
var ch = this._input[0];
this.yytext += ch;
this.yyleng++;
this.offset++;
this.match += ch;
this.matched += ch;
var lines = ch.match(/(?:\r\n?|\n).*/g);
if (lines) {
this.yylineno++;
this.yylloc.last_line++;
} else {
this.yylloc.last_column++;
}
if (this.options.ranges) {
this.yylloc.range[1]++;
}
this._input = this._input.slice(1);
return ch;
},
// unshifts one char (or a string) into the input
unput:function (ch) {
var len = ch.length;
var lines = ch.split(/(?:\r\n?|\n)/g);
this._input = ch + this._input;
this.yytext = this.yytext.substr(0, this.yytext.length - len);
//this.yyleng -= len;
this.offset -= len;
var oldLines = this.match.split(/(?:\r\n?|\n)/g);
this.match = this.match.substr(0, this.match.length - 1);
this.matched = this.matched.substr(0, this.matched.length - 1);
if (lines.length - 1) {
this.yylineno -= lines.length - 1;
}
var r = this.yylloc.range;
this.yylloc = {
first_line: this.yylloc.first_line,
last_line: this.yylineno + 1,
first_column: this.yylloc.first_column,
last_column: lines ?
(lines.length === oldLines.length ? this.yylloc.first_column : 0)
+ oldLines[oldLines.length - lines.length].length - lines[0].length :
this.yylloc.first_column - len
};
if (this.options.ranges) {
this.yylloc.range = [r[0], r[0] + this.yyleng - len];
}
this.yyleng = this.yytext.length;
return this;
},
// When called from action, caches matched text and appends it on next action
more:function () {
this._more = true;
return this;
},
// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
reject:function () {
if (this.options.backtrack_lexer) {
this._backtrack = true;
} else {
return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
text: "",
token: null,
line: this.yylineno
});
}
return this;
},
// retain first n characters of the match
less:function (n) {
this.unput(this.match.slice(n));
},
// displays already matched input, i.e. for error messages
pastInput:function () {
var past = this.matched.substr(0, this.matched.length - this.match.length);
return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
},
// displays upcoming input, i.e. for error messages
upcomingInput:function () {
var next = this.match;
if (next.length < 20) {
next += this._input.substr(0, 20-next.length);
}
return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
},
// displays the character position where the lexing error occurred, i.e. for error messages
showPosition:function () {
var pre = this.pastInput();
var c = new Array(pre.length + 1).join("-");
return pre + this.upcomingInput() + "\n" + c + "^";
},
// test the lexed token: return FALSE when not a match, otherwise return token
test_match:function (match, indexed_rule) {
var token,
lines,
backup;
if (this.options.backtrack_lexer) {
// save context
backup = {
yylineno: this.yylineno,
yylloc: {
first_line: this.yylloc.first_line,
last_line: this.last_line,
first_column: this.yylloc.first_column,
last_column: this.yylloc.last_column
},
yytext: this.yytext,
match: this.match,
matches: this.matches,
matched: this.matched,
yyleng: this.yyleng,
offset: this.offset,
_more: this._more,
_input: this._input,
yy: this.yy,
conditionStack: this.conditionStack.slice(0),
done: this.done
};
if (this.options.ranges) {
backup.yylloc.range = this.yylloc.range.slice(0);
}
}
lines = match[0].match(/(?:\r\n?|\n).*/g);
if (lines) {
this.yylineno += lines.length;
}
this.yylloc = {
first_line: this.yylloc.last_line,
last_line: this.yylineno + 1,
first_column: this.yylloc.last_column,
last_column: lines ?
lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
this.yylloc.last_column + match[0].length
};
this.yytext += match[0];
this.match += match[0];
this.matches = match;
this.yyleng = this.yytext.length;
if (this.options.ranges) {
this.yylloc.range = [this.offset, this.offset += this.yyleng];
}
this._more = false;
this._backtrack = false;
this._input = this._input.slice(match[0].length);
this.matched += match[0];
token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
if (this.done && this._input) {
this.done = false;
}
if (token) {
return token;
} else if (this._backtrack) {
// recover context
for (var k in backup) {
this[k] = backup[k];
}
return false; // rule action called reject() implying the next rule should be tested instead.
}
return false;
},
// return next match in input
next:function () {
if (this.done) {
return this.EOF;
}
if (!this._input) {
this.done = true;
}
var token,
match,
tempMatch,
index;
if (!this._more) {
this.yytext = '';
this.match = '';
}
var rules = this._currentRules();
for (var i = 0; i < rules.length; i++) {
tempMatch = this._input.match(this.rules[rules[i]]);
if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
match = tempMatch;
index = i;
if (this.options.backtrack_lexer) {
token = this.test_match(tempMatch, rules[i]);
if (token !== false) {
return token;
} else if (this._backtrack) {
match = false;
continue; // rule action called reject() implying a rule MISmatch.
} else {
// else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
return false;
}
} else if (!this.options.flex) {
break;
}
}
}
if (match) {
token = this.test_match(match, rules[index]);
if (token !== false) {
return token;
}
// else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
return false;
}
if (this._input === "") {
return this.EOF;
} else {
return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
text: "",
token: null,
line: this.yylineno
});
}
},
// return next match that has a token
lex:function lex() {
var r = this.next();
if (r) {
return r;
} else {
return this.lex();
}
},
// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
begin:function begin(condition) {
this.conditionStack.push(condition);
},
// pop the previously active lexer condition state off the condition stack
popState:function popState() {
var n = this.conditionStack.length - 1;
if (n > 0) {
return this.conditionStack.pop();
} else {
return this.conditionStack[0];
}
},
// produce the lexer rule set which is active for the currently active lexer condition state
_currentRules:function _currentRules() {
if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
} else {
return this.conditions["INITIAL"].rules;
}
},
// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
topState:function topState(n) {
n = this.conditionStack.length - 1 - Math.abs(n || 0);
if (n >= 0) {
return this.conditionStack[n];
} else {
return "INITIAL";
}
},
// alias for begin(condition)
pushState:function pushState(condition) {
this.begin(condition);
},
// return the number of states currently on the stack
stateStackSize:function stateStackSize() {
return this.conditionStack.length;
},
options: {},
performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
var YYSTATE=YY_START;
switch($avoiding_name_collisions) {
case 0:/* skip whitespace */
break;
case 1:return 6
break;
case 2:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2); return 4
break;
case 3:return 17
break;
case 4:return 18
break;
case 5:return 23
break;
case 6:return 24
break;
case 7:return 22
break;
case 8:return 21
break;
case 9:return 10
break;
case 10:return 11
break;
case 11:return 8
break;
case 12:return 14
break;
case 13:return 'INVALID'
break;
}
},
rules: [/^(?:\s+)/,/^(?:(-?([0-9]|[1-9][0-9]+))(\.[0-9]+)?([eE][-+]?[0-9]+)?\b)/,/^(?:"(?:\\[\\"bfnrt/]|\\u[a-fA-F0-9]{4}|[^\\\0-\x09\x0a-\x1f"])*")/,/^(?:\{)/,/^(?:\})/,/^(?:\[)/,/^(?:\])/,/^(?:,)/,/^(?::)/,/^(?:true\b)/,/^(?:false\b)/,/^(?:null\b)/,/^(?:$)/,/^(?:.)/],
conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],"inclusive":true}}
});
return lexer;
})();
parser.lexer = lexer;
function Parser () {
this.yy = {};
}
Parser.prototype = parser;parser.Parser = Parser;
return new Parser;
})();
if (typeof require !== 'undefined' && typeof exports !== 'undefined') {
exports.parser = parser;
exports.Parser = parser.Parser;
exports.parse = function () { return parser.parse.apply(parser, arguments); };
}

View File

@@ -0,0 +1,34 @@
{
"author": "Zach Carter <zach@carter.name> (http://zaa.ch)",
"name": "@mapbox/jsonlint-lines-primitives",
"description": "Validate JSON",
"keywords": [
"json",
"validation",
"lint",
"jsonlint"
],
"version": "2.0.2",
"preferGlobal": true,
"repository": {
"type": "git",
"url": "git://github.com/mapbox/jsonlint.git"
},
"bugs": {
"url": "http://github.com/mapbox/jsonlint/issues"
},
"main": "lib/jsonlint.js",
"engines": {
"node": ">= 0.6"
},
"dependencies": {},
"devDependencies": {
"test": "*",
"jison": "*",
"uglify-js": "*"
},
"scripts": {
"test": "node test/all-tests.js"
},
"optionalDependencies": {}
}

View File

@@ -0,0 +1,8 @@
var fs = require('fs');
var source = "var jsonlint = (function(){var require=true,module=false;var exports={};" +
fs.readFileSync(__dirname+'/../lib/jsonlint.js', 'utf8') +
"return exports;})()";
console.log(source);

View File

@@ -0,0 +1,24 @@
int "-"?([0-9]|[1-9][0-9]+)
exp [eE][-+]?[0-9]+
frac "."[0-9]+
%%
\s+ /* skip whitespace */
{int}{frac}?{exp}?\b return 'NUMBER'
\"(?:'\\'[\\"bfnrt/]|'\\u'[a-fA-F0-9]{4}|[^\\\0-\x09\x0a-\x1f"])*\" yytext = yytext.substr(1,yyleng-2); return 'STRING'
"{" return '{'
"}" return '}'
"[" return '['
"]" return ']'
"," return ','
":" return ':'
"true" return 'TRUE'
"false" return 'FALSE'
"null" return 'NULL'
<<EOF>> return 'EOF'
. return 'INVALID'
%%

View File

@@ -0,0 +1,110 @@
%start JSONText
/*
ECMA-262 5th Edition, 15.12.1 The JSON Grammar.
*/
%%
JSONString
: STRING
{ // replace escaped characters with actual character
$$ = new String(yytext.replace(/\\(\\|")/g, "$"+"1")
.replace(/\\n/g,'\n')
.replace(/\\r/g,'\r')
.replace(/\\t/g,'\t')
.replace(/\\v/g,'\v')
.replace(/\\f/g,'\f')
.replace(/\\b/g,'\b'));
$$.__line__ = @$.first_line;
}
;
JSONNumber
: NUMBER
{
$$ = new Number(yytext);
$$.__line__ = @$.first_line;
}
;
JSONNullLiteral
: NULL
{
$$ = null;
}
;
JSONBooleanLiteral
: TRUE
{
$$ = new Boolean(true);
$$.__line__ = @$.first_line;
}
| FALSE
{
$$ = new Boolean(false);
$$.__line__ = @$.first_line;
}
;
JSONText
: JSONValue EOF
{return $$ = $1;}
;
JSONValue
: JSONNullLiteral
| JSONBooleanLiteral
| JSONString
| JSONNumber
| JSONObject
| JSONArray
;
JSONObject
: '{' '}'
{$$ = {}; Object.defineProperty($$, '__line__', {
value: @$.first_line,
enumerable: false
})}
| '{' JSONMemberList '}'
{$$ = $2; Object.defineProperty($$, '__line__', {
value: @$.first_line,
enumerable: false
})}
;
JSONMember
: JSONString ':' JSONValue
{$$ = [$1, $3];}
;
JSONMemberList
: JSONMember
{{$$ = {}; $$[$1[0]] = $1[1];}}
| JSONMemberList ',' JSONMember
{$$ = $1; $1[$3[0]] = $3[1];}
;
JSONArray
: '[' ']'
{$$ = []; Object.defineProperty($$, '__line__', {
value: @$.first_line,
enumerable: false
})}
| '[' JSONElementList ']'
{$$ = $2; Object.defineProperty($$, '__line__', {
value: @$.first_line,
enumerable: false
})}
;
JSONElementList
: JSONValue
{$$ = [$1];}
| JSONElementList ',' JSONValue
{$$ = $1; $1.push($3);}
;

View File

@@ -0,0 +1,210 @@
var fs = require("fs"),
assert = require("assert"),
parser = require("../lib/jsonlint").parser;
exports["test string with line break"] = function () {
var json = '{"foo": "bar\nbar"}';
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test string literal"] = function () {
var json = '"foo"';
assert.equal(parser.parse(json), "foo");
assert.equal(parser.parse(json).__line__, 1);
};
exports["test number literal"] = function () {
var json = '1234';
assert.equal(parser.parse(json), 1234);
assert.equal(parser.parse(json).__line__, 1);
};
exports["test null literal"] = function () {
var json = 'null';
assert.equal(parser.parse(json), null);
};
exports["test boolean literal"] = function () {
var json = 'true';
assert.equal(parser.parse(json), true);
assert.equal(parser.parse(json).__line__, 1);
};
exports["test unclosed array"] = function () {
var json = fs.readFileSync(__dirname + "/fails/2.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test unquotedkey keys must be quoted"] = function () {
var json = fs.readFileSync(__dirname + "/fails/3.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test extra comma"] = function () {
var json = fs.readFileSync(__dirname + "/fails/4.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test double extra comma"] = function () {
var json = fs.readFileSync(__dirname + "/fails/5.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test missing value"] = function () {
var json = fs.readFileSync(__dirname + "/fails/6.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test comma after the close"] = function () {
var json = fs.readFileSync(__dirname + "/fails/7.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test extra close"] = function () {
var json = fs.readFileSync(__dirname + "/fails/8.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test extra comma after value"] = function () {
var json = fs.readFileSync(__dirname + "/fails/9.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test extra value after close with misplaced quotes"] = function () {
var json = fs.readFileSync(__dirname + "/fails/10.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test illegal expression addition"] = function () {
var json = fs.readFileSync(__dirname + "/fails/11.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test illegal invocation of alert"] = function () {
var json = fs.readFileSync(__dirname + "/fails/12.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test numbers cannot have leading zeroes"] = function () {
var json = fs.readFileSync(__dirname + "/fails/13.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test numbers cannot be hex"] = function () {
var json = fs.readFileSync(__dirname + "/fails/14.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test illegal backslash escape \\0"] = function () {
var json = fs.readFileSync(__dirname + "/fails/15.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test unquoted text"] = function () {
var json = fs.readFileSync(__dirname + "/fails/16.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test illegal backslash escape \\x"] = function () {
var json = fs.readFileSync(__dirname + "/fails/17.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test missing colon"] = function () {
var json = fs.readFileSync(__dirname + "/fails/19.json")
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test double colon"] = function () {
var json = fs.readFileSync(__dirname + "/fails/20.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test comma instead of colon"] = function () {
var json = fs.readFileSync(__dirname + "/fails/21.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test colon instead of comma"] = function () {
var json = fs.readFileSync(__dirname + "/fails/22.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test bad raw value"] = function () {
var json = fs.readFileSync(__dirname + "/fails/23.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test single quotes"] = function () {
var json = fs.readFileSync(__dirname + "/fails/24.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test tab character in string"] = function () {
var json = fs.readFileSync(__dirname + "/fails/25.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test tab character in string 2"] = function () {
var json = fs.readFileSync(__dirname + "/fails/26.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test line break in string"] = function () {
var json = fs.readFileSync(__dirname + "/fails/27.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test line break in string in array"] = function () {
var json = fs.readFileSync(__dirname + "/fails/28.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test 0e"] = function () {
var json = fs.readFileSync(__dirname + "/fails/29.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test 0e+"] = function () {
var json = fs.readFileSync(__dirname + "/fails/30.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test 0e+ 1"] = function () {
var json = fs.readFileSync(__dirname + "/fails/31.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test comma instead of closing brace"] = function () {
var json = fs.readFileSync(__dirname + "/fails/32.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
};
exports["test bracket mismatch"] = function () {
var json = fs.readFileSync(__dirname + "/fails/33.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
}
exports["test extra brace"] = function () {
var json = fs.readFileSync(__dirname + "/fails/34.json").toString();
assert["throws"](function () {parser.parse(json)}, "should throw error");
}
exports["test pass-1"] = function () {
var json = fs.readFileSync(__dirname + "/passes/1.json").toString();
assert.doesNotThrow(function () {parser.parse(json)}, "should pass");
}
exports["test pass-2"] = function () {
var json = fs.readFileSync(__dirname + "/passes/2.json").toString();
assert.doesNotThrow(function () {parser.parse(json)}, "should pass");
}
exports["test pass-3"] = function () {
var json = fs.readFileSync(__dirname + "/passes/3.json").toString();
assert.doesNotThrow(function () {parser.parse(json)}, "should pass");
}
if (require.main === module) {
require("test").run(exports);
}

View File

@@ -0,0 +1 @@
{"Extra value after close": true} "misplaced quoted value"

View File

@@ -0,0 +1 @@
{"Illegal expression": 1 + 2}

View File

@@ -0,0 +1 @@
{"Illegal invocation": alert()}

View File

@@ -0,0 +1 @@
{"Numbers cannot have leading zeroes": 013}

View File

@@ -0,0 +1 @@
{"Numbers cannot be hex": 0x14}

View File

@@ -0,0 +1 @@
["Illegal backslash escape: \x15"]

View File

@@ -0,0 +1 @@
[\naked]

View File

@@ -0,0 +1 @@
["Illegal backslash escape: \017"]

View File

@@ -0,0 +1 @@
{"Missing colon" null}

View File

@@ -0,0 +1 @@
["Unclosed array"

View File

@@ -0,0 +1 @@
{"Double colon":: null}

View File

@@ -0,0 +1 @@
{"Comma instead of colon", null}

View File

@@ -0,0 +1 @@
["Colon instead of comma": false]

View File

@@ -0,0 +1 @@
["Bad value", truth]

View File

@@ -0,0 +1 @@
['single quote']

View File

@@ -0,0 +1 @@
[" tab character in string "]

View File

@@ -0,0 +1 @@
["tab\ character\ in\ string\ "]

View File

@@ -0,0 +1,2 @@
["line
break"]

View File

@@ -0,0 +1,2 @@
["line\
break"]

View File

@@ -0,0 +1 @@
[0e]

View File

@@ -0,0 +1 @@
{unquoted_key: "keys must be quoted"}

View File

@@ -0,0 +1 @@
[0e+]

View File

@@ -0,0 +1 @@
[0e+-1]

View File

@@ -0,0 +1 @@
{"Comma instead if closing brace": true,

View File

@@ -0,0 +1 @@
["mismatch"}

View File

@@ -0,0 +1 @@
{"extra brace": 1}}

View File

@@ -0,0 +1 @@
["extra comma",]

View File

@@ -0,0 +1 @@
["double extra comma",,]

View File

@@ -0,0 +1 @@
[ , "<-- missing value"]

View File

@@ -0,0 +1 @@
["Comma after the close"],

View File

@@ -0,0 +1 @@
["Extra close"]]

View File

@@ -0,0 +1 @@
{"Extra comma": true,}

View File

@@ -0,0 +1,58 @@
[
"JSON Test Pattern pass1",
{"object with 1 member":["array with 1 element"]},
{},
[],
-42,
true,
false,
null,
{
"integer": 1234567890,
"real": -9876.543210,
"e": 0.123456789e-12,
"E": 1.234567890E+34,
"": 23456789012E66,
"zero": 0,
"one": 1,
"space": " ",
"quote": "\"",
"backslash": "\\",
"controls": "\b\f\n\r\t",
"slash": "/ & \/",
"alpha": "abcdefghijklmnopqrstuvwyz",
"ALPHA": "ABCDEFGHIJKLMNOPQRSTUVWYZ",
"digit": "0123456789",
"0123456789": "digit",
"special": "`1~!@#$%^&*()_+-={':[,]}|;.</>?",
"hex": "\u0123\u4567\u89AB\uCDEF\uabcd\uef4A",
"true": true,
"false": false,
"null": null,
"array":[ ],
"object":{ },
"address": "50 St. James Street",
"url": "http://www.JSON.org/",
"comment": "// /* <!-- --",
"# -- --> */": " ",
" s p a c e d " :[1,2 , 3
,
4 , 5 , 6 ,7 ],"compact":[1,2,3,4,5,6,7],
"jsontext": "{\"object with 1 member\":[\"array with 1 element\"]}",
"quotes": "&#34; \u0022 %22 0x22 034 &#x22;",
"\/\\\"\uCAFE\uBABE\uAB98\uFCDE\ubcda\uef4A\b\f\n\r\t`1~!@#$%^&*()_+-=[]{}|;:',./<>?"
: "A key can be any string"
},
0.5 ,98.6
,
99.44
,
1066,
1e1,
0.1e1,
1e-1,
1e00,2e+00,2e-00
,"rosebud"]

View File

@@ -0,0 +1 @@
[[[[[[[[[[[[[[[[[[["Not too deep"]]]]]]]]]]]]]]]]]]]

View File

@@ -0,0 +1,6 @@
{
"JSON Test Pattern pass3": {
"The outermost value": "must be an object or array.",
"In this test": "It is an object."
}
}

View File

@@ -0,0 +1,334 @@
/*jslint evil: true, strict: false */
/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
lastIndex, length, parse, prototype, push, replace, slice, stringify,
test, toJSON, toString, valueOf
*/
// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.
if (!this.JSON) {
this.JSON = {};
}
(function () {
function f(n) {
// Format integers to have at least two digits.
return n < 10 ? '0' + n : n;
}
if (typeof Date.prototype.toJSON !== 'function') {
Date.prototype.toJSON = function (key) {
return isFinite(this.valueOf()) ?
this.getUTCFullYear() + '-' +
f(this.getUTCMonth() + 1) + '-' +
f(this.getUTCDate()) + 'T' +
f(this.getUTCHours()) + ':' +
f(this.getUTCMinutes()) + ':' +
f(this.getUTCSeconds()) + 'Z' : null;
};
String.prototype.toJSON =
Number.prototype.toJSON =
Boolean.prototype.toJSON = function (key) {
return this.valueOf();
};
}
var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
gap,
indent,
meta = { // table of character substitutions
'\b': '\\b',
'\t': '\\t',
'\n': '\\n',
'\f': '\\f',
'\r': '\\r',
'"' : '\\"',
'\\': '\\\\'
},
rep;
function quote(string) {
// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.
escapable.lastIndex = 0;
return escapable.test(string) ?
'"' + string.replace(escapable, function (a) {
var c = meta[a];
return typeof c === 'string' ? c :
'\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
}) + '"' :
'"' + string + '"';
}
function str(key, holder) {
// Produce a string from holder[key].
var i, // The loop counter.
k, // The member key.
v, // The member value.
length,
mind = gap,
partial,
value = holder[key];
// If the value has a toJSON method, call it to obtain a replacement value.
if (value && typeof value === 'object' &&
typeof value.toJSON === 'function') {
value = value.toJSON(key);
}
// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.
if (typeof rep === 'function') {
value = rep.call(holder, key, value);
}
// What happens next depends on the value's type.
switch (typeof value) {
case 'string':
return quote(value);
case 'number':
// JSON numbers must be finite. Encode non-finite numbers as null.
return isFinite(value) ? String(value) : 'null';
case 'boolean':
case 'null':
// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.
return String(value);
// If the type is 'object', we might be dealing with an object or an array or
// null.
case 'object':
// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.
if (!value) {
return 'null';
}
// Make an array to hold the partial results of stringifying this object value.
gap += indent;
partial = [];
// Is the value an array?
if (Object.prototype.toString.apply(value) === '[object Array]') {
// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.
length = value.length;
for (i = 0; i < length; i += 1) {
partial[i] = str(i, value) || 'null';
}
// Join all of the elements together, separated with commas, and wrap them in
// brackets.
v = partial.length === 0 ? '[]' :
gap ? '[\n' + gap +
partial.join(',\n' + gap) + '\n' +
mind + ']' :
'[' + partial.join(',') + ']';
gap = mind;
return v;
}
// If the replacer is an array, use it to select the members to be stringified.
if (rep && typeof rep === 'object') {
length = rep.length;
for (i = 0; i < length; i += 1) {
k = rep[i];
if (typeof k === 'string') {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
} else {
// Otherwise, iterate through all of the keys in the object.
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = str(k, value);
if (v) {
partial.push(quote(k) + (gap ? ': ' : ':') + v);
}
}
}
}
// Join all of the member texts together, separated with commas,
// and wrap them in braces.
v = partial.length === 0 ? '{}' :
gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
mind + '}' : '{' + partial.join(',') + '}';
gap = mind;
return v;
}
}
// If the JSON object does not yet have a stringify method, give it one.
if (typeof JSON.stringify !== 'function') {
JSON.stringify = function (value, replacer, space) {
// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.
var i;
gap = '';
indent = '';
// If the space parameter is a number, make an indent string containing that
// many spaces.
if (typeof space === 'number') {
for (i = 0; i < space; i += 1) {
indent += ' ';
}
// If the space parameter is a string, it will be used as the indent string.
} else if (typeof space === 'string') {
indent = space;
}
// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.
rep = replacer;
if (replacer && typeof replacer !== 'function' &&
(typeof replacer !== 'object' ||
typeof replacer.length !== 'number')) {
throw new Error('JSON.stringify');
}
// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.
return str('', {'': value});
};
}
// If the JSON object does not yet have a parse method, give it one.
if (typeof JSON.parse !== 'function') {
JSON.parse = function (text, reviver) {
// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.
var j;
function walk(holder, key) {
// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.
var k, v, value = holder[key];
if (value && typeof value === 'object') {
for (k in value) {
if (Object.hasOwnProperty.call(value, k)) {
v = walk(value, k);
if (v !== undefined) {
value[k] = v;
} else {
delete value[k];
}
}
}
}
return reviver.call(holder, key, value);
}
// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.
cx.lastIndex = 0;
if (cx.test(text)) {
text = text.replace(cx, function (a) {
return '\\u' +
('0000' + a.charCodeAt(0).toString(16)).slice(-4);
});
}
// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.
// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.
j = eval('(' + text + ')');
// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.
return typeof reviver === 'function' ?
walk({'': j}, '') : j;
}
// If the text is not JSON parseable, then a SyntaxError is thrown.
throw new SyntaxError('JSON.parse');
};
}
}());

View File

@@ -0,0 +1,59 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>JSON Lint</title>
<script src="json2.js"></script>
<script src="jsonlint.js"></script>
<script>
window.onload = function () {
document.getElementById("button").onclick = function () {
try {
var result = jsonlint.parse(document.getElementById("source").value);
if (result) {
document.getElementById("result").innerHTML = "JSON is valid!";
document.getElementById("result").className = "pass";
if (document.getElementById("reformat").checked) {
document.getElementById("source").value = JSON.stringify(result, null, " ");
}
}
} catch(e) {
document.getElementById("result").innerHTML = e;
document.getElementById("result").className = "fail";
}
};
}
</script>
<style>
body {font-family: sans-serif;}
#result {
padding: 1em;
}
.pass {
background-color: #efe;
color: #393;
border: 2px solid #393;
}
.fail {
background-color: #fee;
color: #933;
border: 2px solid #933;
}
textarea { width: 100%; }
</style>
</head>
<body>
<h1>JSON Lint</h1>
<p>A pure JavaScript version of the service provided at <a href="http://jsonlint.com/">jsonlint.com</a>.</p>
<textarea id="source" rows="20" cols="50">
</textarea>
<p>
<button id="button">Validate</button>
<input type="checkbox" value="yes" id="reformat" /><label for="reformat">reformat JSON</label>
</p>
<h2>Results</h2>
<pre id="result"></pre>
<p><a href="http://github.com/zaach/jsonlint">project on github</a></p>
</body>
</html>

File diff suppressed because one or more lines are too long