├── .gitignore ├── .gitmodules ├── LICENSE.txt ├── README.md ├── example ├── ScreenShot.png ├── color-switching.gif ├── demo.css ├── demo.js ├── realtime-color.gif └── scope-coloring.html ├── scope.js ├── scope.test.js └── test └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # WebStorm # 3 | ################### 4 | .idea* 5 | 6 | # OS generated files # 7 | ###################### 8 | .DS_Store 9 | .DS_Store? 10 | ._* 11 | .Spotlight-V100 12 | .Trashes 13 | Icon? 14 | ehthumbs.db 15 | Thumbs.db -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "JSLint"] 2 | path = JSLint 3 | url = https://github.com/douglascrockford/JSLint.git 4 | [submodule "CodeMirror"] 5 | path = CodeMirror 6 | url = https://github.com/marijnh/CodeMirror.git 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | (The MIT License) 2 | 3 | Copyright (c) 2013 Daniel Lamb 4 | http://daniellamb.mit-license.org/ 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining 7 | a copy of this software and associated documentation files (the 8 | 'Software'), to deal in the Software without restriction, including 9 | without limitation the rights to use, copy, modify, merge, publish, 10 | distribute, sublicense, and/or sell copies of the Software, and to 11 | permit persons to whom the Software is furnished to do so, subject to 12 | the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be 15 | included in all copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, 18 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 19 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 20 | IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 21 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 22 | TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 23 | SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #JavaScript Scope Context Coloring 2 | 3 | A JavaScript experiment in switching between syntax highlighting and scope colorizing, built on JSLint and CodeMirror and inspired by Douglas Crockford. 4 | 5 | ##[Check out the Live Examples](http://daniellmb.github.io/JavaScript-Scope-Context-Coloring/example/scope-coloring.html#fullmonad) 6 | 7 | It's [not perfect yet](http://daniellmb.github.io/JavaScript-Scope-Context-Coloring/test/), pull requests welcome! :) 8 | 9 | ###Switch between Syntax Highlighting and Scope Context Coloring 10 | [![JavaScript Scope Context Coloring Switching](http://daniellmb.github.io/JavaScript-Scope-Context-Coloring/example/color-switching.gif "JavaScript Scope Context Coloring Switching")](http://daniellmb.github.io/JavaScript-Scope-Context-Coloring/example/scope-coloring.html#fullmonad) 11 | 12 | ###Real-time Scope Context Coloring 13 | [![JavaScript Scope Context Coloring](http://daniellmb.github.io/JavaScript-Scope-Context-Coloring/example/realtime-color.gif "Real-time JavaScript Scope Context Coloring")](http://daniellmb.github.io/JavaScript-Scope-Context-Coloring/example/scope-coloring.html#level10) -------------------------------------------------------------------------------- /example/ScreenShot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniellmb/JavaScript-Scope-Context-Coloring/39ced9c7c4642a232514424ad99ae3ba1a01ff59/example/ScreenShot.png -------------------------------------------------------------------------------- /example/color-switching.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniellmb/JavaScript-Scope-Context-Coloring/39ced9c7c4642a232514424ad99ae3ba1a01ff59/example/color-switching.gif -------------------------------------------------------------------------------- /example/demo.css: -------------------------------------------------------------------------------- 1 | /* editor scope levels */ 2 | .cm-level0 { color: #ffffff; } /* white */ 3 | .cm-level1 { color: #aefab8; } /* green */ 4 | .cm-level2 { color: #fafa99; } /* yellow */ 5 | .cm-level3 { color: #a9f2ff; } /* blue */ 6 | .cm-level4 { color: #faadba; } /* red */ 7 | .cm-level5 { color: #c9bfff; } /* cyan */ 8 | .cm-level6 { color: #f9b5ff; } /* magenta */ 9 | .cm-level7 { color: #c7c7c7; } /* gray */ 10 | .cm-level8 { color: #ffc47d; } /* orange */ 11 | .cm-level9 { color: #cf9369; } /* crap-brown */ 12 | 13 | /* You don't need below this line, it's just for this demo page */ 14 | 15 | /* sticky footer */ 16 | html, body { height: 100%; } 17 | #wrap { min-height: 100%; height: auto !important; height: 100%; margin: 0 auto -60px; } 18 | #push, #footer { height: 60px; } 19 | #footer { text-align: center; } 20 | .container .credit { margin: 20px 0; } 21 | 22 | /* change mode */ 23 | #toggleMode { margin-bottom: 5px; } 24 | 25 | /* samples */ 26 | form { position: relative; } 27 | .samples { position: absolute; top:3px; right:3px; z-index: 5; } 28 | 29 | /* editor */ 30 | .CodeMirror { height: auto; } 31 | .CodeMirror-scroll { overflow-y: hidden; overflow-x: auto; } 32 | 33 | /* scope color legend */ 34 | .legend span { font-size: 12px; border: solid 1px #000; padding: 2px; } 35 | .level0 { background-color: #ffffff; } 36 | .level1 { background-color: #aefab8; } 37 | .level2 { background-color: #fafa99; } 38 | .level3 { background-color: #a9f2ff; } 39 | .level4 { background-color: #faadba; } 40 | .level5 { background-color: #c9bfff; } 41 | .level6 { background-color: #f9b5ff; } 42 | .level7 { background-color: #c7c7c7; } 43 | .level8 { background-color: #ffc47d; } 44 | .level9 { background-color: #CF9369; } 45 | 46 | /* fork on github */ 47 | #fork a { 48 | background: #000; 49 | color: #fff; 50 | text-decoration: none; 51 | font-family: arial, sans-serif; 52 | text-align: center; 53 | font-weight: bold; 54 | padding: 5px 40px; 55 | font-size: 1rem; 56 | line-height: 2rem; 57 | position: relative; 58 | transition: 0.5s; 59 | } 60 | 61 | #fork a:hover { 62 | background: #060; 63 | color: #fff; 64 | } 65 | 66 | #fork a::before,#fork a::after { 67 | content: ""; 68 | width: 100%; 69 | display: block; 70 | position: absolute; 71 | top: 1px; 72 | left: 0; 73 | height: 1px; 74 | background: #fff; 75 | } 76 | 77 | #fork a::after { 78 | bottom: 1px; 79 | top: auto; 80 | } 81 | 82 | @media screen and (min-width:767px) { 83 | 84 | #fork { 85 | position: absolute; 86 | display: block; 87 | top: 0; 88 | right: 0; 89 | width: 200px; 90 | overflow: hidden; 91 | height: 200px; 92 | z-index:10; 93 | } 94 | 95 | #fork a { 96 | width: 200px; 97 | position: absolute; 98 | top: 60px; 99 | right: -60px; 100 | transform: rotate(45deg); 101 | -webkit-transform: rotate(45deg); 102 | box-shadow: 4px 4px 10px rgba(0,0,0,0.8); 103 | } 104 | 105 | #footer { 106 | margin-left: -20px; 107 | margin-right: -20px; 108 | padding-left: 20px; 109 | padding-right: 20px; 110 | } 111 | } -------------------------------------------------------------------------------- /example/demo.js: -------------------------------------------------------------------------------- 1 | /*global jQuery, JSLINT, CodeMirror, location*/ 2 | (function ($, lint, cm) { 3 | 'use strict'; 4 | 5 | var editor, 6 | editorChanged = false, 7 | scopeMode = { 8 | 'name': 'scope', 9 | 'hasChanged': function () { 10 | return editorChanged; 11 | }, 12 | 'getLevels': function () { 13 | //check that JSLINT has been called 14 | if (lint.hasOwnProperty('errors')) { 15 | editorChanged = false; 16 | return lint.color(lint.data()); 17 | } 18 | } 19 | }; 20 | 21 | //set up code editor 22 | editor = cm($('#editor')[0], { 'theme': 'ambiance', 'mode': scopeMode }); 23 | 24 | //lint the code in the editor 25 | function lintCode() { 26 | lint(editor.getValue()); 27 | editorChanged = true; 28 | } 29 | 30 | //lint code when it changes 31 | editor.on('change', lintCode); 32 | 33 | 34 | //Shouldn't need the code below this line, it's just for this demo 35 | 36 | function loadSample(id) { 37 | var sample = $('#' + id.substr(id.lastIndexOf('#') + 1)); 38 | editor.setValue(sample.text()); 39 | } 40 | 41 | //load the default code sample 42 | $(function() { 43 | loadSample(location.hash || 'depinject'); 44 | }); 45 | 46 | //support changing modes 47 | function selectMode(mode) { 48 | if (mode === 'pro') { 49 | lintCode(); 50 | editor.setOption('mode', scopeMode); 51 | } else { 52 | editor.setOption('mode', 'javascript'); 53 | } 54 | } 55 | 56 | //bind mode change event handler 57 | $('#toggleMode').on('click', 'button', function() { 58 | selectMode($(this).data('mode')); 59 | }); 60 | 61 | //bind code sample change handler 62 | $('#samples').on('click', 'a', function() { 63 | loadSample(this.href); 64 | }); 65 | 66 | }(jQuery, JSLINT, CodeMirror)); -------------------------------------------------------------------------------- /example/realtime-color.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/daniellmb/JavaScript-Scope-Context-Coloring/39ced9c7c4642a232514424ad99ae3ba1a01ff59/example/realtime-color.gif -------------------------------------------------------------------------------- /example/scope-coloring.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | JavaScript Scope Context Coloring 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 | 20 |

An experiment in switching between syntax highlighting and scope colorizing built on JSLint and CodeMirror.

21 | Fork me on GitHub 22 |
23 | 24 | 25 |
26 | 27 |
28 |
29 | 30 | 35 |
36 |
37 |
38 |
39 |
40 |
41 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 77 | 78 | 79 | 104 | 142 | 162 | 163 | -------------------------------------------------------------------------------- /scope.js: -------------------------------------------------------------------------------- 1 | /*global CodeMirror */ 2 | CodeMirror.defineMode('scope', function (config, parserConfig) { 3 | 'use strict'; 4 | 5 | var levels = [], fullyParsed; 6 | 7 | //request levels from parserConfig 8 | function getLevels() { 9 | //this gets called a lot so check first 10 | if (parserConfig.hasChanged()) { 11 | levels = parserConfig.getLevels(); 12 | } 13 | } 14 | 15 | function parseLine(stream, state) { 16 | var level; 17 | 18 | //get the latest levels 19 | getLevels(); 20 | 21 | //check for valid levels data 22 | if (state.index < levels.length) { 23 | 24 | //check stream for 'start of line' 25 | if (stream.sol()) { 26 | state.line += 1; 27 | } 28 | 29 | //get the level information 30 | level = levels[state.index]; 31 | 32 | //check if we need to skip the line 33 | if (state.line !== level.line) { 34 | //do nothing 35 | stream.skipToEnd(); 36 | return null; 37 | } 38 | 39 | //check if we need to skip ahead within the line 40 | if ((level.from - 1) > stream.pos) { 41 | stream.pos += (level.from - stream.pos - 1); 42 | return null; 43 | } 44 | 45 | //move stream to scope change 46 | stream.pos += (level.thru - level.from); 47 | 48 | //step forward in the list 49 | state.index += 1; 50 | 51 | //return the CSS class to apply 52 | return 'level' + level.level; 53 | } 54 | //do nothing 55 | stream.skipToEnd(); 56 | return null; 57 | } 58 | 59 | return { 60 | startState: function () { 61 | fullyParsed = false; 62 | return { 63 | //source code line 64 | line: 0, 65 | //levels array index 66 | index: 0 67 | }; 68 | }, 69 | token: function (stream, state) { 70 | return parseLine(stream, state); 71 | }, 72 | blankLine: function(state) { 73 | return parseLine(new CodeMirror.StringStream(''), state); 74 | } 75 | }; 76 | }); -------------------------------------------------------------------------------- /scope.test.js: -------------------------------------------------------------------------------- 1 | /*global CodeMirror, JSLINT, test*/ 2 | (function() { 3 | 'use strict'; 4 | 5 | //set up mocks 6 | var editorChanged = false, 7 | mockLevels = [], 8 | hasChangedCallCount = 0, 9 | getLevelsCallCount = 0, 10 | scopeMode = { 11 | 'name': 'scope', 12 | 'hasChanged': function () { 13 | //instrument for tests 14 | hasChangedCallCount += 1; 15 | 16 | return editorChanged; 17 | }, 18 | 'getLevels': function () { 19 | //instrument for tests 20 | getLevelsCallCount += 1; 21 | //reset flag till next change 22 | editorChanged = false; 23 | return mockLevels; 24 | } 25 | }, 26 | mode = CodeMirror.getMode({tabSize: 4}, scopeMode); 27 | 28 | //test helpers 29 | function beforeEach() { 30 | editorChanged = false; 31 | hasChangedCallCount = 0; 32 | getLevelsCallCount = 0; 33 | mockLevels = []; 34 | } 35 | 36 | function testOutput(name, levels, tokens) { 37 | test.mode(name, mode, tokens, function() { 38 | //set to true so the levels will be looked up 39 | editorChanged = true; 40 | mockLevels = levels; 41 | }); 42 | } 43 | 44 | function testLevels(name, code, output) { 45 | test.mode(name, mode, output, function() { 46 | //set to true so the levels will be looked up 47 | editorChanged = true; 48 | JSLINT(code); 49 | mockLevels = JSLINT.color(JSLINT.data()); 50 | }); 51 | } 52 | 53 | function findSingle(str, pos, ch) { 54 | for (;;) { 55 | var found = str.indexOf(ch, pos); 56 | if (found == -1) return null; 57 | if (str.charAt(found + 1) != ch) return found; 58 | pos = found + 2; 59 | } 60 | } 61 | 62 | var styleName = /[\w&-_]+/g; 63 | function parseTokens(strs) { 64 | var tokens = [], plain = ""; 65 | for (var i = 0; i < strs.length; ++i) { 66 | if (i) plain += "\n"; 67 | var str = strs[i], pos = 0; 68 | while (pos < str.length) { 69 | var style = null, text; 70 | if (str.charAt(pos) == "[" && str.charAt(pos+1) != "[") { 71 | styleName.lastIndex = pos + 1; 72 | var m = styleName.exec(str); 73 | style = m[0].replace(/&/g, " "); 74 | var textStart = pos + style.length + 2; 75 | var end = findSingle(str, textStart, "]"); 76 | if (end == null) throw new Error("Unterminated token at " + pos + " in '" + str + "'" + style); 77 | text = str.slice(textStart, end); 78 | pos = end + 1; 79 | } else { 80 | var end = findSingle(str, pos, "["); 81 | if (end == null) end = str.length; 82 | text = str.slice(pos, end); 83 | pos = end; 84 | } 85 | text = text.replace(/\[\[|\]\]/g, function(s) {return s.charAt(0);}); 86 | tokens.push(style, text); 87 | plain += text; 88 | } 89 | } 90 | return {tokens: tokens, plain: plain}; 91 | } 92 | 93 | test.mode = function(name, mode, tokens, setUp) { 94 | var data = parseTokens(tokens); 95 | return test(mode.name + " mode " + name, function() { 96 | beforeEach(); 97 | if (setUp) {setUp(); } 98 | return compare(data.plain, data.tokens, mode); 99 | }); 100 | }; 101 | 102 | function compare(text, expected, mode) { 103 | 104 | var expectedOutput = []; 105 | for (var i = 0; i < expected.length; i += 2) { 106 | var sty = expected[i]; 107 | if (sty && sty.indexOf(" ")) sty = sty.split(' ').sort().join(' '); 108 | expectedOutput.push(sty, expected[i + 1]); 109 | } 110 | 111 | var observedOutput = highlight(text, mode); 112 | 113 | var pass, passStyle = ""; 114 | pass = highlightOutputsEqual(expectedOutput, observedOutput); 115 | passStyle = pass ? 'mt-pass' : 'mt-fail'; 116 | 117 | var s = ''; 118 | if (pass) { 119 | s += '
'; 120 | s += '
' + text + '
'; 121 | s += '
'; 122 | s += prettyPrintOutputTable(observedOutput); 123 | s += '
'; 124 | s += '
'; 125 | return s; 126 | } else { 127 | s += '
'; 128 | s += '
' + text + '
'; 129 | s += '
'; 130 | s += 'expected:'; 131 | s += prettyPrintOutputTable(expectedOutput); 132 | s += 'observed:'; 133 | s += prettyPrintOutputTable(observedOutput); 134 | s += '
'; 135 | s += '
'; 136 | throw s; 137 | } 138 | } 139 | 140 | /** 141 | * Emulation of CodeMirror's internal highlight routine for testing. Multi-line 142 | * input is supported. 143 | * 144 | * @param string to highlight 145 | * 146 | * @param mode the mode that will do the actual highlighting 147 | * 148 | * @return array of [style, token] pairs 149 | */ 150 | function highlight(string, mode) { 151 | var state = mode.startState() 152 | 153 | var lines = string.replace(/\r\n/g,'\n').split('\n'); 154 | var st = [], pos = 0; 155 | for (var i = 0; i < lines.length; ++i) { 156 | var line = lines[i], newLine = true; 157 | var stream = new CodeMirror.StringStream(line); 158 | if (line == "" && mode.blankLine) mode.blankLine(state); 159 | /* Start copied code from CodeMirror.highlight */ 160 | while (!stream.eol()) { 161 | var style = mode.token(stream, state), substr = stream.current(); 162 | if (style && style.indexOf(" ") > -1) style = style.split(' ').sort().join(' '); 163 | 164 | stream.start = stream.pos; 165 | if (pos && st[pos-2] == style && !newLine) { 166 | st[pos-1] += substr; 167 | } else if (substr) { 168 | st[pos++] = style; st[pos++] = substr; 169 | } 170 | // Give up when line is ridiculously long 171 | if (stream.pos > 5000) { 172 | st[pos++] = null; st[pos++] = this.text.slice(stream.pos); 173 | break; 174 | } 175 | newLine = false; 176 | } 177 | } 178 | 179 | return st; 180 | } 181 | 182 | /** 183 | * Compare two arrays of output from highlight. 184 | * 185 | * @param o1 array of [style, token] pairs 186 | * 187 | * @param o2 array of [style, token] pairs 188 | * 189 | * @return boolean; true iff outputs equal 190 | */ 191 | function highlightOutputsEqual(o1, o2) { 192 | if (o1.length != o2.length) return false; 193 | for (var i = 0; i < o1.length; ++i) 194 | if (o1[i] != o2[i]) return false; 195 | return true; 196 | } 197 | 198 | /** 199 | * Print tokens and corresponding styles in a table. Spaces in the token are 200 | * replaced with 'interpunct' dots (·). 201 | * 202 | * @param output array of [style, token] pairs 203 | * 204 | * @return html string 205 | */ 206 | function prettyPrintOutputTable(output) { 207 | var s = ''; 208 | s += ''; 209 | for (var i = 0; i < output.length; i += 2) { 210 | var style = output[i], val = output[i+1]; 211 | s += 212 | ''; 217 | } 218 | s += ''; 219 | for (var i = 0; i < output.length; i += 2) { 220 | s += ''; 221 | } 222 | s += '
' + 213 | '' + 214 | val.replace(/ /g,'\xb7') + 215 | '' + 216 | '
' + output[i] + '
'; 223 | return s; 224 | } 225 | 226 | 227 | 228 | 229 | 230 | 231 | //Finally, we can test things! ... I miss AngularJS 232 | 233 | //basic logic tests 234 | (function() { 235 | var message = "should call hasChanged to see if the editor has changed"; 236 | test(mode.name + " " + message, function() { 237 | beforeEach(); 238 | highlight(" ", mode); 239 | return eq(hasChangedCallCount, 1, message); 240 | }); 241 | }()); 242 | 243 | (function() { 244 | var message = "should not call getLevels if the editor has not changed"; 245 | test(mode.name + " " + message, function() { 246 | beforeEach(); 247 | highlight(" ", mode); 248 | return eq(getLevelsCallCount, 0, message); 249 | }); 250 | }()); 251 | 252 | (function() { 253 | var message = "should call getLevels if the editor has changed"; 254 | test(mode.name + " " + message, function() { 255 | beforeEach(); 256 | editorChanged = true; 257 | highlight(" ", mode); 258 | return eq(getLevelsCallCount, 1, message); 259 | }); 260 | }()); 261 | 262 | /* 263 | * output tests are registered by calling testOutput(testName, levels, 264 | * tokens), where levels is the array returned by JSLINT.color, and 265 | * tokens is an array of lines that make up the test. 266 | * 267 | * These lines are strings, in which styled stretches of code are 268 | * enclosed in brackets `[]`, and prefixed by their style. For 269 | * example, `[keyword if]`. 270 | */ 271 | 272 | testOutput("should apply the class name based on the level array", 273 | [{"line": 1, "level": 1, "from": 1, "thru": 14}], 274 | ["[level1 var test = 0;]"]); 275 | 276 | testOutput("should walk though the levels array", 277 | [ 278 | {"line": 1, "level": 10, "from": 1, "thru": 4}, 279 | {"line": 1, "level": 20, "from": 4, "thru": 14} 280 | ], 281 | ["[level10 var][level20 test = 1;]"]); 282 | 283 | testOutput("should handle gaps in scope change", 284 | [ 285 | {"line": 1, "level": 200, "from": 1, "thru": 4}, 286 | {"line": 1, "level": 300, "from": 6, "thru": 15} 287 | ], 288 | ["[level200 var] [level300 test = 2;]"]); 289 | 290 | testOutput("should properly tokenize MONAD", 291 | [ 292 | {"line": 1, "level": 1, "from": 1, "thru": 9}, 293 | {"line": 1, "level": 0, "from": 10, "thru": 15}, 294 | {"line": 1, "level": 1, "from": 15, "thru": 19}, 295 | {"line": 2, "level": 1, "from": 5, "thru": 18}, 296 | {"line": 3, "level": 1, "from": 5, "thru": 11}, 297 | {"line": 3, "level": 2, "from": 12, "thru": 20}, 298 | {"line": 3, "level": 1, "from": 21, "thru": 25}, 299 | {"line": 3, "level": 2, "from": 25, "thru": 34}, 300 | {"line": 4, "level": 2, "from": 9, "thru": 20}, 301 | {"line": 4, "level": 0, "from": 21, "thru": 27}, 302 | {"line": 4, "level": 2, "from": 27, "thru": 41}, 303 | {"line": 5, "level": 2, "from": 9, "thru": 21}, 304 | {"line": 5, "level": 3, "from": 22, "thru": 39}, 305 | {"line": 6, "level": 3, "from": 13, "thru": 25}, 306 | {"line": 6, "level": 2, "from": 25, "thru": 30}, 307 | {"line": 6, "level": 3, "from": 30, "thru": 32}, 308 | {"line": 7, "level": 3, "from": 9, "thru": 11}, 309 | {"line": 8, "level": 2, "from": 9, "thru": 22}, 310 | {"line": 9, "level": 2, "from": 5, "thru": 7}, 311 | {"line": 10, "level": 1, "from": 1, "thru": 2} 312 | ], 313 | [ 314 | "[level1 function] [level0 MONAD][level1 () {]", 315 | " [level1\"use strict\";]", 316 | " [level1 return] [level2 function] [level1 unit][level2 (value) {]", 317 | " [level2 var monad =] [level0 Object][level2 .create(null);]", 318 | " [level2 monad.bind =] [level3 function (func) {]", 319 | " [level3 return func(][level2 value][level3 );]", 320 | " [level3 };]", 321 | " [level2 return monad;]", 322 | " [level2 };]", 323 | "[level1 }]" 324 | ] 325 | ); 326 | 327 | testLevels("should handle blank source code lines", 328 | [ //input 329 | "function MONAD() {", 330 | "",//empty line 331 | "",//empty line 332 | " return function unit(value) {", 333 | "",//empty line 334 | " var monad = Object.create(null);", 335 | " monad.bind = function (func) {", 336 | " return func(value);", 337 | " };", 338 | " return monad;", 339 | "",//empty line 340 | " };", 341 | "}" 342 | ], 343 | [ //expected output 344 | "[level1 function] [level0 MONAD][level1 () {]", 345 | "",//empty line 346 | "",//empty line 347 | " [level1 return] [level2 function unit(value) {]", 348 | "",//empty line 349 | " [level2 var monad =] [level0 Object][level2 .create(null);]", 350 | " [level2 monad.bind =] [level3 function (func) {]", 351 | " [level3 return func(][level2 value][level3 );]", 352 | " [level3 };]", 353 | " [level2 return monad;]", 354 | "",//empty line 355 | " [level2 };]", 356 | "[level1 }]" 357 | ] 358 | ); 359 | 360 | //GitHub Issue #3 (bug FIXED in JSLINT) 361 | testLevels("should properly parse named function expressions", 362 | [ //input 363 | "var foo = function bar() {", 364 | " console.log('baz');", 365 | "};" 366 | ], 367 | [ //expected output 368 | "[level0 var foo =] [level1 function bar() {]", 369 | " [level1 console.log('baz');]", 370 | "[level1 };]" 371 | ] 372 | ); 373 | 374 | //GitHub Issue #6 (bug FIXED in editor) 375 | testLevels("should handle gaps in level lines", 376 | [ //input 377 | "function MONAD() {", 378 | " //ignore me", 379 | " return function unit(value) {", 380 | " /*", 381 | " ignore me", 382 | " */", 383 | " var monad = Object.create(null);", 384 | " monad.bind = function (func) {", 385 | " return func(value);", 386 | " };", 387 | " return monad;", 388 | " };", 389 | "}" 390 | ], 391 | [ //expected output 392 | "[level1 function] [level0 MONAD][level1 () {]", 393 | " //ignore me", 394 | " [level1 return] [level2 function unit(value) {]", 395 | " /*", 396 | " ignore me", 397 | " */", 398 | " [level2 var monad =] [level0 Object][level2 .create(null);]", 399 | " [level2 monad.bind =] [level3 function (func) {]", 400 | " [level3 return func(][level2 value][level3 );]", 401 | " [level3 };]", 402 | " [level2 return monad;]", 403 | " [level2 };]", 404 | "[level1 }]" 405 | ] 406 | ); 407 | 408 | //OPEN ISSUES 409 | 410 | // GitHub Issue #2 (bug in JSLINT) 411 | testLevels("should match variables more than once per scope", 412 | [ //input 413 | "var x = 10;", 414 | "function level0() {", 415 | " var a = x,", 416 | " b = x;", 417 | " return function level1() {", 418 | " return x + x;", 419 | " };", 420 | "}" 421 | ], 422 | [ //expected output 423 | "[level0 var x = 10;]", 424 | "[level1 function] [level0 level0][level1 () {]", 425 | " [level1 var a =] [level0 x][level1 ,]", 426 | " [level1 b =] [level0 x][level1 ;]", 427 | " [level1 return] [level2 function] [level1 level1][level2 () {]", 428 | " [level2 return] [level0 x] [level2 +] [level0 x][level2 ;]", 429 | " [level2 };]", 430 | "[level1 }]" 431 | ] 432 | ); 433 | 434 | }()); -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Scope Mode Unit Tests 6 | 7 | 8 | 9 | 32 | 33 | 34 |

Scope Mode Unit Tests

35 | 36 |
37 |
Ran 0 of 0 tests
38 |
39 |

Please enable JavaScript...

40 |
41 | 42 |
43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 166 | 167 | 168 | --------------------------------------------------------------------------------