By Peter van der Zee, © pvdz.ee, 2011. See REPL here.
This tool will give you as much feedback about js as possible. It does this by static analysis and immediate visual feedback. Zeon works purely on the client side (in your browser) using a handwritten js parser, written in js. The parser is fast enough to process large scripts in real-time at acceptable speeds.
There are many things that can be reported. Not all of them will be equally important, but most of them could be a sign of possible trouble in your code. Pretty much all the (relevant) warnings from JSLint are also in this project, and many many more.
Errors and warnings
The parser will try to report errors as detailed as possible. This is not always the case. Sometimes an error is an open end and pretty much anything could follow. But most of the time a certain character is missing from a syntactic structure (like a parenthesis from a statement header) and this is easy to report. All errors are marked with a red circle, most of the time at the appropriate position. Every now and then you'll have to go a little forward or backwards to get to the actual error position. Use the navigation to see the actual error message.
The warnings and messages are displayed inline in the code. Most of them appear as small circles in the top-left corner of whatever caused the warning to appear. Typing is shown through colored lines under various variables. Of course, regular syntax highlighting is also applied. Missing semi-colons which would be substituted by ASI are noted as small red squares.
Variables
Whenever you use a variable before declaring it, you'll see a warning. Using a variable that's not been declared at all causes a warning. Double declarations do too. Shadowing might be a sign of a problem so you'll see a warning for that too.
Scope lookup
A certain set of bubbles are caused by scope lookups. Any variable will have a bubble that tells you how many scope lookups js would have to do to get to that variable. Note that you'll always at least do one scope lookup. If the variable is not defined, the bubble will be red. Variables that are not defined, and not known to be global or part of the language, will be striked out.
Type inference
There is some type inference going on. It's not perfect yet, but already goes a long way. The inference takes most assignments into account, for as far as the asignee type(s) are determinable of course. And even though the return type for functions can be determined, this only happens in the last step of the process so the return type of function calls are not taken into account for variable typing. If the type of a sub expression can be determined, it will properly propagate through operators.
Editor
Zeon is also a minor editor. I say minor because I basically only did that which I required myself. Building an IDE in the browser is a major PITA and I'm not touching it much further beyond what I have. Tabbing is supported. Tabbing multiple lines in and out is too (!). Putting your cursor on a token with some meta information (like type) will show a pop up with more information.
Menu
In the lower right corner, the big Z cannot be missed, you'll find the menu. This menu gives you access to four things. Information, navigation, tools and configuration. I would say toolination, but that wouldn't make sense.
The information is pretty basic right now. Just the size of the document and the number of lines. I do want to add the current position and selected size, but that's not in right now :)
Navigation is a powerful tool of going through your code and looking at potential problems quickly. Where most other (external) tools force you to go back and forward between the code and the analyze results, Zeon allows you to just click next. Or previous. Or just click the number to go to that result. You can also click on some of the titles in then menu-nav to get a filtered list of unique results.
There are also a few tools available from the menu.
Beautify and minify
The beautifier can take any valid piece of code and generate properly indented code from it. It will remove the comments though, something I intend to fix in a future release.
The minifier will do the exact opposite, it will remove all whitespace and comments for as far as syntactically legal. It will also reduce variable names, unless you disabled that in the config. The config also contains an option to replace semi's with a newline (unless that would not cause ASI), idea courtesy of Fabian Jakobs (@fjakobs).
The minifier will not rewrite code (like uglifyjs or the closure compiler do). That's something I do want to investigate later, but haven't yet.
Raw parse tree
You can also look at the raw parse tree in textual form, if you can make sense of it. Since the current internal representation of the "parse tree" is just a set of objects and arrays with properties for information, the shown parse tree is simple though explicit. Just keep in mind that this is all Zeon needs to do most of its magic.
Remove Zeon
You can remove this Zeon instance from the page and attempt to restore the textarea to it's former state. Will work in most cases, but there are some styles (like border) that are too much work to restore, so it might not always work very properly (whatever, right).
Pragma processing
You can process the pragma's in the current page. It will remove the pragma's and do what they're intended to do.
Profiling
This is a tool that's just a proof of concept, since it needs real embedding in an app (or an extension that can catch script loads and replace them with a custom script). But the tool allows you to rewrite a certain script, add custom call methods to each statement, run the code and visually display hot code with color bars. You'll immediately get a sense of what is important in your code and what isn't. The current tool will just do the converting and add the boiler plate for displaying the results in Zeon inline. It relies on Zeon being loaded and handle to it globally available. So to use it, paste some script that can run on its own, run the transformator and run the result in the console of your browser in the current page.
Maximize and minimize
In the Z menu you can also maximize and minize Zeon with the plus button, and then schrink it with the minus button.
Generate JSDocs
You can let Zeon use its internal information about the current script and generate JSDocs from it. It will add JSDocs to functions and variables. If there was a prior JSDoc, it will copy the comments (but replace type information). It will also add the jspath of each function (not for variables). Variables are always a single @var
line. If there were multiple vars in the declaration, one line for each var is added to the comment.
Fix hoisting
Hoisting is the concept of variables being declared (but not initialized) at the start of the function/script rather than the actual point of usage. Function declarations are the same except they do initialize at the top as well. This tool attempts to show you in what order your code is actually executed.
First it refactors all the var
statements in your code and puts them at the top of a function or global scope. It will remove the original var
keyword and any variable name that did not have an initializer (the assignment). This is perfectly equivalent to the original code and is a so called "best practice". Albeit a boyscouts one. Note that this operation can leave some weird indentation, but it tries as much as it can.
Optionally (config -- "hoisting fix moves func decl to top") it will also move function declarations to the top of the scope (but below variable declarations). This too is perfectly equivalent.
Trim whitespace
Removes all trailing whitespace from the current document. It's like taking out the trash.
Inject calls
Transforms the current code by injecting call to a function supply (through very simple prompt) to every statement. The argument will be a unique id for that statement, throughout the source code. This allows you to do some stuff for code coverage or statistics with your code. The transformed code is restructured (much like the beautifier) and loses all comments. The callback can currently not be specified. Both are things I intend to change in the future.
Show branching
This tool will take a function (any function in your current code) and derive all functions with a unique branching path through that function. So when you have an if
statement, it will generate at least two functions; one with and one without the if
. Careful though because with large scripts or many code paths this number increases exponentially and your browser might not be able to handle it. Although interesting, I've not found it very useful ;)
Be careful because big scripts with complex branching structures can explode the computations and resulting size. You're on your own here :)
Extract method
By request of Christian Johansen (@cjno) I've added the "Extract Method" tool you can find in Eclipse. The way it works in Zeon is you select a part of the code and press the button. Zeon will then generate a new function for you, containing the selected code. Any variable that was declared but not within the function will be added as a parameter to this new function. Any variable that you declare or was passed on will be returned as an array. If the array would only contain one element, only that element is returned instead. If it would be empty, the function does not explicitly return at all. After this function a function call is made with the same variables as those its expecting. This allows the function to integrate fluently. The generated call will also create any variables created inside the function and assign their return value from the array. You can obviously remove what you deem you don't need anymore. The resulting function and boiler plate will try to keep the entire code flow in tact. This tool might help you to refactor your code :)
Note that you can select snippets of code that will make no sense to refactor like this. For instance, you cannot go cross function and expect things to work fine. Having a return
, continue
or break
in the selection will also not work properly. I could fix (using try
/catch
and labels) that but the resulting code would be quite useless so you'll have to do that yourslef ;) So always make sure your selection does not break something that pairs (like parenthesis, curly braces or square brackets). The tool will not even try to detect this, so no warnings.
Disambiguate operators
When you run this tool it will add parenthesis around any expression that might otherwise be ambiguous if you're not very familiar with operator precedence. This means that something like a + b + c
becomes (a + b) + c
. Likewise, a + b * c
becomes a + (b * c)
. And a * b & c % d || !e % f
becomes ((a * b) & (c % d)) || (!e % f)
. So this tool shows you what the order of applying operators actually is when you execute it in js.
To and From js string
Converts js code to a valid js string. If you paste a js string (just the string with the quotes), you can convert that back to script. When converting from js string, it will remove the first and last character of the script (the quotes), so make sure there's no trailing whitespace.
To bookmarklet
Create a bookmarklet from the current code. Will not change the variable names so you can debug it. You can always first minify and then create the bookmarklet.
Config
The configuration allows you to toggle certain visual aids, warning messages and the way some of the tools work.
Bookmarklet
Drag this bookmarklet to your bookmark bar to enable Zeon for any textarea on any page when you click on it.
Chrome Extension
You can use this Zeon Chrome Extension to get all the script tags on a page and load them in Zeon. It should work on local files too, but I can't seem to get that to work.
Warnings
This is a list of messages that Zeon might throw at you. A lot of them were copied from JSLint (which is not as easy as it sounds..) and many more have been added since. Every message will have an explanation on when the message will be shown. The order is pretty random ;)
- "ASI"
- Non-fatal missing semi-colon. Marked with a red marker (can be disabled). These are semi-colons js will automatically insert for you, but which is bad to rely on because you can run into problems when you think js will add one when it won't.
- "unused"
- A variable that has been declared (either with
var
or as a function declaration, or both) but never actually been used. Why declare a variable if you won't use it? - "missing block good"
- A statement (like
if
orwhile
) followed by another statement (if (x) statement
) which is not wrapped in a block{}
, but put on the same line as the previous statement. - "missing block bad"
- Same as before, but the next statement is put on the next line, which might cause way more confusion. Single line it or add a block.
- "empty statement"
- A semi-colon without a body is an empty statement. They are not bad per se (in fact, they don't do anything) but this is to warn you for possible improper usage of semi-colons. Especially semi's after function declarations and blocks are useless and may be removed.
- "assignment in header"
- Should be obvious.
if (x = 5) ...
- "weak comparison"
- Douglas' favorite warning. This is what you'll see in most cases whenever you use
==
instead of===
(or!=
vs!==
). - "dangling underscore"
- Whenever a variable ends with an underscore
_
. - "dot and not can be confusing"
- Using a dot or the negative character class in regular expressions:
/ab.c/
and/ab[^c]d/
- "inc dec operator"
- Using the pre- / postfix operator
++
and--
, when you just as easily could have usedx += 1
. - "cannot inc/dec on call expression"
- For doing silly stuff like
x()++
- "inc/dec only valid on vars"
- The incremental and decremental operator is only valid on actual variables. This because it needs to store a value back in the variable. Even
(x,y)++
is invalid, because the comma fetchesy
so++
can't get a reference anymore. - "binary operator"
- Whenever you use binary operators (
| & ^ ~
). - "use dot access"
- Whenever you do dynamic property access with a string literal that would be a valid identifier:
obj['foo']
- "continue only in loops"
- The
continue
statement may only occur inside a loop. - "break needs loop or label"
- The
break
statement may occur inside a loop and outside a loop. But (and only) outside of loops the statement requires a label. - "return only in function"
- The
return
statement is only allowed inside functions. - "flow statement"
- Whenever you use
return
,continue
orbreak
in a context where it would not be allowed. Return can only be used in functions. Continue can only be used in loops. Break can only be used in loops or with an existing label within the required range. - "trailing decimal"
- Whenever you use a trailing dot in a number literal. Valid but silly and confusing.
- "leading decimal"
- Whenever you use a leading dot in a number literal. Valid but silly and confusing.
- "regex confusion"
- Using the compound operator
/=
could lead to confusing with respect to regular expression literals. - "number dot"
- Using the dot operator on number literals is dangerous, even if you know why it is.
- "assignment bad"
- I'm not sure how you can trigger this one. You have to mess up reeeeally bad to get to this point ;) Assigning to a function call, for instance.
f() = 5;
- "assignment this"
- Yeah, don't do this.
- "bad string escapement"
- There are only a hand full of characters that are actually valid. The rest is ignored. They are different from regex.
- "bad regex escapement"
- There are only a hand full of characters that are actually valid. The rest is ignored. They are different from strings.
- "avoid hex"
- Hexidecimal notation can be confusing to some people:
0x5F0
- "caller callee"
- This is depricated and will throw errors in strict mode.
- "octal escape"
- This is confusing and depricated, and throws errors in strict mode (starting a number literal with a zero, followed by at least one other digit):
0778
- "00"
- It's pointless to add multiple zeroes when.
- "regexp call"
- When calling a regular expression directly. Only firefox supported this and they removed that support, so there.
- "confusing plusses"
- Doing silly stuff like
x + ++y;
- "confusing minusses"
- Doing silly stuff like
x - --y;
- "control char"
- Using control chars literally in a string can be dangerous in certain contexts. Use escapes characters or unicode escapes to substitute them.
- "unsafe char"
- Using certain chars literally in a string can be dangerous in certain contexts. Use escapes characters or unicode escapes to substitute them.
- "invalid unicode escape in string"
- When you start a unicode escape but don't properly finish it.
"foo\u0bar"
- "invalid hex escape in string"
- When starting a hex escape but don't finish it.
"foo\xbeer"
- "catch var assignment"
- The variable of a
catch
clause is technically put in its own scope. Don't assign to it because it's likely to break stuff eventually. - "bad constructor"
- Invoking
Object
,String
,Number
,Boolean
,Function
,RegExp
orError
with new. Use their primitive/literal versions instead. For errors, just use your own error objects.Function
is like using eval. - "array constructor"
- Invoking Array as a constructor is bad because it can be confusing. In almost all cases it will just create a new array with the parameters as items, but there is an edge case. If you only supply one argument and that argument is a number, a new empty (!) array with its
.length
set to that number will be created. The number is then discarded, so it does not appear in the array. That's why you should always just use[]
to create arrays. - "error constructor"
- Error is a built in object. While not bad per se, you can throw anything you'd like. So why not create your own error objects. Or maybe even just throw plain strings for descriptive messages?
- "very bad constructor"
- Invoking
Math
orJSON
with new. - "Function is eval"
- Calling
Function
. - "function wrapped"
- Douglas wants a specific way of wrapping anonymous function expressions.
(function(){}())
vs(function(){})()
, although I think Zeon currently doesn't detect this properly. - "document.write"
- Go away.
- "iteration function"
- Do not create functions inside a loop. This leads to closure problems.
- "empty block"
- Silly.
- "eval"
- Avoid whenever possible. Zeon ignores the presence of
eval
(since it's impossible to take it into account). If you use eval, Zeon's analysis might b0rk. - "empty regex char class"
- Using
[]
inside a regex. - "fell through case"
- Can lead to problems. If you do this anyways, add a comment stating so.
- "extra comma"
- Two consecutive comma's lead to confusion and might be a typo. It also has bad support amongst (even slightly) older browsers.
- "trailing comma"
- An object or array literal that ends with a comma (before the closing
}]
) is fine, but most slightly older browsers might not support it and simply throw a syntax error. If you're sure your targeted browsers support it, feel free to ignore this :) - "double new"
- Why would you want to do this?
- "double delete"
- Why would you want to do this?
- "undefined"
- This is not a program error ;) This is about undefined being a global variable and usage of it is relatively dangerous. Just compare to null instead.
- "duplicate objlit prop"
- When doing
{x:1, x:2}
, the firstx
property is ignored (x
will be2
). This can be very valueable when defining prototype property of a large object. - "comma screws ref"
- Using something like
(1, x.y)
will screw up the context forx.y
. - "timer eval"
- Passing on a string to setTimeout or setInterval effectively causes an eval.
- "group vars"
- Douglas wants you to group variable declarations (at the top too, if at all possible).
- "func decl at top"
- Function declarations should be at the top to prevent confusion with hoisting.
- "is label"
- Just a heads up that this is currently interpreted by js as a label (not, for instance, a property name..).
- "math call"
Math()
- "new wants parens"
new Person;
is valid but confusing for people new to js.- "missing radix"
- Always supply the radix when calling
parseInt
. - "nested comment"
- Whenever
/*
is found in another comment. - "new statement"
- Starting a statement with
new
. - "no delete vars"
- Don't delete vars, only properties. If you want to delete globals, just delete properties from window.
- "silly delete construct"
- Using delete twice in the same expression (where one delete would be part of the operand of the other delete).
- "delete not a function"
- The
delete
keyword is actually an operator and doesn't require parenthesis. And since you can only (usefully) usedelete
on variables and properties of variables, it doesn't ever make sense to wrap them in parentheses while on the other hand possibly spreading confusion. So don't use parentheses after delete. - "weird delete operand"
- When using delete with a primitive, a reserved keyword or basically anything that doesn't start with a variable (and not covered by other warnings).
- "dont use __proto__"
- Warns for
__proto__
property. Don't use it. Sure as hell don't rely on it. - "empty switch"
- Valid but stupid.
- "quasi empty switch"
switch
with empty clauses.- 'idle switch'
switch
with only a default clause. Might as well ditch the switch.- "empty clause"
- Adding a clause with no body. Can easily appear for simple fall through cases.
- "clause should break"
- When a
switch
clause does notreturn
,continue
,throw
orbreak
. - "switch is an if"
- Single clause
switch
. - "unwrapped for-in"
- Always wrap the body of a
for
-in
statement in aif (obj.hasOwnProperty(key)) ...
- "in out of for"
- There are very few real use cases for using the
in
operator outside of a for statement. - "use {}"
- When doing
new Object
- "use []"
- When doing
new Array
- "double block"
- Putting a block inside another block,
{ .. { ... } ... }
- "useless block"
- Putting a block in a function body,
function(){ .. { .. } .. }
- "use capital namespacing"
- When a constructor was detected whos name doesn't start with a capital.
- "constructor called as function"
- Objects detected to be constructors (used
new
on them or accessed theprototype
property), called as a regular function. - "bad asi pattern"
- Relying on ASI when the next token would be a opening parenthesis,
a\n(b)
- "unlikely typeof result"
- Checking the result of typeof with a string that's not one of the standard ones.
- "weird typeof op"
- When using a weird operator on the result of typeof,
typeof x > y
- "typeof always string"
- When using
===
or!==
where==
or!=
would be equally safe. - "static expression"
- An expression that only exists of primitives (and operators).
- "static condition"
- Condition that only exists of primitives (and operators)
- "is dev relic"
- Identifiers with development like names. Stuff like
console
,debugger
,foo
, ... - "pragma requires name parameter"
- Most pragmas need at least one parameter.
//#define foo
- "pragma requires value parameter"
- Some pragmas also need a second parameter.
//#macro foo bar
- "missing ifdef"
- When encountering an elseifdef, elsedef or endif without having seen a leading ifdef.
- "missing inline"
- When encountering an endline without having seen a leading inline.
- "pragma start missing end"
- Incomplete ifdef chains.
- "macro name should be identifier"
- Due to some limitations, macro names must be valid identifier names, or property access (so a dot is allowed).
//#macro foo.bar.baz.phoey this is fine
. - "useless multiple throw args"
- Passing on multiple arguments to throw is useless because it only returns one (the last expressions of the expressions).
- "unnecessary parentheses"
- Using parentheses after operators or throw statements is silly and useless. Throw only returns one expression (so a group is useless). Using parentheses give the wrong impression that
delete
andthrow
might be functions. - "uninitialized value in loop"
- When you create a variable (in or outside a loop) and you read from it in the loop before assigning to it, you could run into trouble. What if the variable becomes some value you did not expect, on the next iteration you might end up with an unexpected result. Always make sure you explicitly initialize your variables, even if it's just to
null
, or evenundefined
. - "prop not declared on proto"
- Encountered a property on a
this
keyword but did not find it explicitly declared on the prototype where it belonged to. It's wise to declare all properties on the prototype so that you know what to expect (and maybe even document them ;). This will not only help yourself, but also anyone else who has to read your code, to know what properties that object instance might get. This warning might generate a few false positives, still need to tweak this a little. - "label not found"
- Used a label which was not found in the label set where it must exist. Browsers should throw an error if labels are used but not defined since they won't know where to "jump" to.
- "duplicate label"
- Declaring the same label name twice within the same label set.
- "unknown implicit global"
- Using a global variable that hasn't been declared and was not hardcoded as being present by the specification or in the browser. This warning will not show if you declared the variable later in the same scope. This sometimes triggers false positives wrt browser variables.
- "known implicit global"
- Using a global variable that is hardcoded to exist in either the specification (
parseInt
,isNaN
) or to exist in the browser (window
, etc). You can generally ignore these :) But sometimes... - "useless parens"
- A grouped expression with just one expression becomes a useless group. For instance
log((5));
is a useless group. The parentheses might as well not exist. - "dead code"
- Either code that cannot be reached due to a premature exit (by
return
,throw
,break
orcontinue
) or an idle statement (a single primitive). Note that for the premature exits, these are checked in branching. So when you have anif
/else
the code following is only dead if both branches exit. Not if only one of them exits. - "premature usage"
- Using a variable before it's declaration. It's valid, but confusing. Try not to do this.
- "cannot call/apply that"
- When encountering parenthesis directly following a primitive, object literal or array literal. Those cannot possibly be a valid call target. The exception might be regular expressions, but that's only supported in FireFox, and they're dropping that support (if they haven't already). So just don't.
- "func expr name is read-only"
- You cannot assign a new value to the name of a function expresssion. So
var f = function name(){ name = 5; };
will not change the value ofname
. If you see this warning, you tried to change the name anyways and it should fail.
Additional reporting
There are a few things also being reported or taken into account.
JSDoc
One of them is JSDocs, albeit very basic right now. However, the type annotation for parameters and variables are taken into account when doing type inference. Whenever you lead a multi line comment with a /**
it will also try to parse and markup the jsdoc. For syntax and more information on JSDoc see: Google Closure, JSDoc toolkit (and its wiki). See also the auto generating of jsdocs.
Labels
Labels are completely taken into account, which gave me new insights to how they work. They are actually a quite complex tax on the language, but luckily this mostly applies to the grammar and not the usage. Zeon will properly warn you when you are inappropriately using labels, or when they would be required (for a break). Double declared labels or invalidly declared labels are marked as such.
Dev relics
As developers we sometimes leave our tools behind. When Zeon sees a developer relic, it will warn you for it. Stuff like console.log
or debugger
should not be left in production code.
Empty statements
Empty statements (when you add a semi colon where one was not required) are marked with an epsilon to inform you of it. They are hardly ever a problem and commonly added after function declarations or certain blocks.
Dangerous naming
Using property names or even variables that use names declared in the specification or commonly found in the browser will end up with a visual warning for them. Even though it's perfectly legal to do obj.if = 5;
, it's really silly unless semantically would demand it.
Dead code
Dead code is also detected. When you return from both branches in an if
-else
, any code that follows will never be executed and is deemed to be "dead code". It will show a skull-and-bones sign before it. The detection is pretty solid, except for try
-catch
, where it might lead to imperfections (due to the dynamic nature of catch
).
Directives
Directives, strings at the start of a function or your script, are marked with a green D. Note that "strict mode"
is currently not taken into account.
Regular expressions
Zeon will attempt to validate your regular expressions. On top of that you'll see grouping help when moving the caret inside a regular expression. That should at least help you to get grouping, class range and quantities correct.
Trailing whitespace
The only whitespace at the ending of a line is a line terminator. All trailing whitespace is marked red for easy destruction. You can also run the trimming tool to remove it all for you :)
Ctrl+click
When you hold the ctrl-key and click on a parenthesis or bracket Zeon will jump to the matching element. If you click on a variable, it will jump to first declaration of that variable (if any).
JSPath
A js document is a structured document. As such, you can define a system to traverse the source code to point to certain concepts. JSPath is something I came up with and is supposed to be the counter part to XPath for CSS. I think I need this system for a tighter type checking system in the future and it sounded like an interesting feature at the time.
There are only a handfull of basic data concepts you might want to care about. Such are variables, functions, objects and arrays. This system should allow you to navigate through the source code, given a certain path, to the correct part of the code.
There are two types of paths; absolute paths and named paths. The absolute paths can be proven to be unique for the source it was defined with. The named paths are usually unique, but can be easily proven to be not to. However, the named path is more user friendly.
Scopes
First of all, scopes are our basic thing. We denote a scope with a forward slash (/
). Everything starts at the global scope, so every path starts with a forward slash. Note that only functions (and catch variables) get their own scope.
Functions
Functions are either denoted by a number surrounded by parenthesis in absolute paths. The number being the nth occurring function in the same scope, offsetting at zero. It may also be denoted as the name of the variable or property where that function was assigned to. This is not always the case, though. Note that the name of a named function expression is not used in this context. So
/(1)/ /foo/
Objects and arrays
Objects and arrays are denoted pretty much the same as functions, except we use curly braces ({}
) for object literals and square brackets ([]
) for array literals.
In the following snippets will show you the absolute and named paths for the last object of each snippet. Of course, the named snippet is ambiguous so should probably not be used in these contexts. They are meant to illustrate the difference.
var obj = {foo:6}; obj = {}; obj = {}; obj = {foo:5}; absolute jspath => /{3}.foo named jspath => /obj.foo var arr = []; arr = []; arr = []; absolute jspath => /[3] named jspath => /arr.5
The order of objects and arrays (and functions) is determined by their opening token. So var obj = { foo: {} };
has two objects. Since the start of the outer object comes before the start of the inner token, the outer token would be /{0}
(or /obj
) and the inner token /{0}.{1}
(or /{0}.foo
or /obj.{1}
or /obj.foo
...).
Inheritance
It's common in the js world to shorten something that's going to be inherited from a constructor by putting a hash between the constructor and the property, rather than .prototype.
. So does jspath. So /Array.prototype.slice
becomes /Array#slice
. That is, it simply replaces any occurrence of .prototype.
with a hash (#
). Yes, that could mean that whenever prototype
was used as a property name but not an actual prototype object, this could be slightly counter intuitive. But in general, I don't think this will cause any problems.
Catch scopes
As mentioned before, the third type of scope in js is the catch scope. It's a bit of a special case because the only thing that can and will be logged in a catch scope is the actual catch variable. New local variables in a catch scope are still logged in the outer scope for the catch scope (in fact, the first outer scope that's not a catch scope). The only part important to remember here is that the catch variable is logged in its own scope. To denote this we use the exclamation mark (!
) followed by a number (the nth catch clause in the same scope) and a forward slash for the scope.
function foo(){ try {} catch(e) {} } /{0}/!0/e /foo/!0/e
Properties
Properties of objects are prefixed with the jspath of the object (which may be another property) and a dot. If a name would be invalid as an identifier or property in js (like numbers or spaces), simply quote them with single or double quotes. In case the name contains quotes, only escape the quotes if they are the same type used to quote the name with a backslash. Always escape a backslash (when part of the name).
/foo.bar /{0}.bar /foo."bar baz" /foo.'bar\'baz'.poo /foo."bar\\\"baz"
When an object literal has a property that's also an object literal, and you want to use the absolute path, the dot is still required: /{0}.{1}.foo
If an object literal has a certain property defined twice or more, it will always refer to the last occurrence of this name.
Pragmas
I'm currently adding pragma support to Zeon. This is a proof of concept for now. Since js does not support any pragmas natively, support has to be "hacked" in. So for now, any pragma has to be added as a single line comment like this: //#define. There may be no space between //, # or the pragma keyword. For brevity, the leading // will be left out in the docs below.
Defines
Defines make it easy to create code that targets specific platforms or product versions (debug, test, release).
A define does or does not exist. You can create one with the #define pragma. Simply stating #define FOO will create FOO and makes it be defined. Whenever you have an #ifdef FOO pragma, it's contents will only be left intact if the define actually exists. Otherwise it is removed. You can also add #elseifdef and #elsedef to this pragma. The #ifdef pragma must always end with a #endif, regardless of any of the other two were used in between.
The pragma process will always remove any //#define comment as well as all //#ifdef, //#elseifdef, //#elsedef and //#endif comments and the entire (source text) line they appear in. This allows you for some initialization or branching code while developing. Additionally, only the ifdef bodies are kept which were indeed defined (and when no ifdef existed, the elsedef body is kept if it exists. Example:
var FOO = false; //#define FOO if (false) { //#ifdef BAR i_am_removed(); } else if (FOO) { //#elseifdef FOO i_will_remain(); } else { //#elsedef i_will_be_gone_because_another_branch_was_ok(); } //#endif
Becomes:
i_will_remain();
Macros
The #macro pragma allows you to replace certain tokens with pretty much anything else. The main use case is replacing constants with their actual value, although other use cases exist. Note that only variable names and property name-chains are possible candidates for this (eg. it will never replace an occurrence in a comment, or string).
The pragma process will remove the //#pragma comment and anything that preceeded it on that line. This allows you to define placeholders in js for the constant or whatever.
Example:
var FOO = 5; //#macro FOO 5 alert(FOO); var constants = { BAR: 1, //#macro BAR 1 BAZ: 2, //#macro BAZ 2 WORLD: 3 //#macro WORLD 3 }; alert(constants.BAR, constants.BAZ, constants.WORLD);
Becomes:
alert(5); alert(1, 2, 3);
Inlining
The #inline pragma will replace all the instances of the given token with the body of the pragma. Just like macros, only variable names and property chains can be replaced. Unlike macros, the entire line of the match will be removed. This also counts for the line of the pragma header and footer. This allows you to do initialization and calling of the function. Since it's very difficult or impossible to determine what you would or would not want to have replaced, we simply remove the entire line. See examples for (hopefully) more clarity.
function foo(){//#inline foo debug("foo"); stuff = log(bar); return stuff; }//#endline function bar(){ stuff = foo(this,is,"foo",bogus,foooo); // and also foo }
Becomes
function bar(){ debug("foo"); log(bar); }
So note that only the actual function call is replaced, none of the other "occurrences" were replaced, because they were not identifiers. The original function is completely stripped.