<html>
<head>
<meta charset="utf-8">
<title>Ember Starter Kit</title>
<link rel="stylesheet" href="http://cdnjs.cloudflare.com/ajax/libs/normalize/2.1.0/normalize.css">
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
<script src="http://builds.emberjs.com/canary/ember.debug.js"></script>
<script src="http://builds.emberjs.com/canary/ember-template-compiler.js"></script>
</head>
<body>
<div id='app'></div>
<pre id="logs"></pre>
<script type="text/x-handlebars" id="application">
{{#if isMorning}} Good morning
{{else if isAfternoon}} Good afternoon
{{else}}
Good night
{{/if}}
</script>
</body>
</html>
/* Put your CSS here */
html, body {
margin: 20px;
}
const VOID_TAGS = { area: true,
base: true,
br: true,
col: true,
command: true,
embed: true,
hr: true,
img: true,
input: true,
keygen: true,
link: true,
meta: true,
param: true,
source: true,
track: true,
wbr: true };
function childrenFor(node) {
if (node.type === 'Program') {
return node.body;
}
if (node.type === 'BlockStatement') {
return node.program.body;
}
if (node.type === 'ElementNode') {
return node.children;
}
}
function isTextNode(node) {
return node.type === 'TextNode';
}
function isCommentStatement(node) {
return node.type === 'CommentStatement';
}
function BlockIndentation(options) {
this.options = options;
this.syntax = null; // set by HTMLBars
// split into a source array (allow windows and posix line endings)
this.source = this.options.rawSource.split(/(?:\r\n?|\n)/g);
}
BlockIndentation.prototype.log = function(results) {
log(results.message);
};
BlockIndentation.prototype.transform = function(ast) {
log('running');
var pluginContext = this;
let b = this.syntax.builders;
let walker = new this.syntax.Walker();
walker.visit(ast, function(node) {
if (pluginContext.detect(node)) {
return pluginContext.process(node);
}
});
return ast;
};
BlockIndentation.prototype.detect = function(node) {
return node.type === 'BlockStatement' || node.type === 'ElementNode';
};
/*eslint no-unused-expressions: 0*/
BlockIndentation.prototype.process = function(node) {
this.validateBlockElse(node);
this.validateBlockEnd(node);
this.validateBlockChildren(node);
},
BlockIndentation.prototype.validateBlockEnd = function(node) {
if (!this.shouldValidateBlockEnd(node)) {
return;
}
var isElementNode = node.type === 'ElementNode';
var displayName = isElementNode ? node.tag : node.path.original;
var display = isElementNode ? '</' + displayName + '>' : '{{/' + displayName + '}}';
var startColumn = node.loc.start.column;
var endColumn = node.loc.end.column;
var startOffset = this.startOffsetFor(node);
var controlCharCount = this.endingControlCharCount(node);
var correctedEndColumn = endColumn - displayName.length - controlCharCount + startOffset;
console.log(correctedEndColumn, startColumn);
if(correctedEndColumn !== startColumn) {
debugger
var startLocation = 'L' + node.loc.start.line + ':C' + node.loc.start.column;
var endLocation = 'L' + node.loc.end.line + ':C' + node.loc.end.column;
var warning = 'Incorrect indentation for `' + displayName + '` beginning at ' + startLocation +
'. Expected `' + display + '` ending at ' + endLocation + ' to be at an indentation of ' + startColumn + ' but ' +
'was found at ' + correctedEndColumn + '.';
this.log({
message: warning,
line: node.loc.end.line,
column: node.loc.end.column,
source: this.sourceForNode(node)
});
}
};
BlockIndentation.prototype.validateBlockChildren = function(node) {
var children = childrenFor(node);
if (!children || !children.length) {
return;
}
// HTML elements that start and end on the same line are fine
if (node.loc.start.line === node.loc.end.line) {
return;
}
var startColumn = node.loc.start.column;
var expectedStartColumn = startColumn + 2;
for (var i = 0; i < children.length; i++) {
var child = children[i];
if (!child.loc) { continue; }
// We might not actually be the first thing on the line. We might be
// preceded by another element or statement, or by some text. So walk
// backwards looking for something else on this line.
var hasLeadingContent = false;
for (var j = i - 1; j >= 0; j--) {
var sibling = children[j];
if (sibling.loc && !isTextNode(sibling)) {
// Found an element or statement. If it's on this line, then we
// have leading content, so set the flag and break. If it's not
// on this line, then we've scanned back to a previous line, so
// we can also break.
if (sibling.loc.end.line === child.loc.start.line) {
hasLeadingContent = true;
}
break;
} else {
var lines = sibling.chars.split(/[\r\n]/);
var lastLine = lines[lines.length - 1];
if (lastLine.trim()) {
// The last line in this text node has non-whitespace content, so
// set the flag.
hasLeadingContent = true;
}
if (lines.length > 1) {
// There are multiple lines meaning we've now scanned back to a
// previous line, so we can break.
break;
}
}
}
if (hasLeadingContent) {
// There's content before us on the same line, so we don't care about
// our column.
continue;
}
var childStartColumn;
// sanitize text node starting column info
if (isTextNode(child)) {
// TextNode's include leading newlines, but those newlines do
// not get used in calculating indentation
var withoutLeadingNewLines = child.chars.replace(/^(\r\n|\n)*/, '');
var firstNonWhitespace = withoutLeadingNewLines.search(/\S/);
// the TextNode is whitespace only, do nothing
if (firstNonWhitespace === -1) { continue; }
// detect if the TextNode starts with `{{`, if it does
// correct for the stripped leading backslash (`\{{foo}}`)
if (child.chars.slice(0, 2) === '{{') {
childStartColumn = child.loc.start.column - 1;
} else {
childStartColumn = firstNonWhitespace;
}
} else {
childStartColumn = child.loc.start.column;
}
if (expectedStartColumn !== childStartColumn) {
var isElementNode = child.type === 'ElementNode';
var display;
if (isElementNode) {
display = '<' + child.tag + '>';
} else if (child.type === 'BlockStatement'){
display = '{{#' + child.path.original + '}}';
} else if (child.type === 'MustacheStatement') {
display = '{{' + child.path.original + '}}';
} else if (isTextNode(child)) {
display = child.chars;
} else if (isCommentStatement(child)) {
display = '<!--' + child.value + '-->';
} else {
display = child.path.original;
}
var startLocation = 'L' + child.loc.start.line + ':C' + child.loc.start.column;
var warning = 'Incorrect indentation for `' + display + '` beginning at ' + startLocation +
'. Expected `' + display + '` to be at an indentation of ' + expectedStartColumn + ' but ' +
'was found at ' + childStartColumn + '.';
this.log({
message: warning,
line: child.loc && child.loc.start.line,
column: child.loc && child.loc.start.column,
source: this.sourceForNode(node)
});
}
}
};
BlockIndentation.prototype.validateBlockElse = function(node) {
if (node.type !== 'BlockStatement' || !node.inverse) {
return;
}
if (this.detectNestedElseIfBlock(node)) {
this.processNestedElseIfBlock(node);
}
var inverse = node.inverse;
var startColumn = node.loc.start.column;
var elseStartColumn = inverse.loc.start.column;
if(elseStartColumn !== startColumn) {
var displayName = node.path.original;
var startLocation = 'L' + node.loc.start.line + ':C' + node.loc.start.column;
var elseLocation = 'L' + inverse.loc.start.line + ':C' + inverse.loc.start.column;
var warning = 'Incorrect indentation for inverse block of `{{#' + displayName + '}}` beginning at ' + startLocation +
'. Expected `{{else}}` starting at ' + elseLocation + ' to be at an indentation of ' + startColumn + ' but ' +
'was found at ' + elseStartColumn + '.';
this.log({
message: warning,
line: inverse.loc.start.line,
column: inverse.loc.start.column,
source: this.sourceForNode(node.inverse)
});
}
};
BlockIndentation.prototype.detectNestedElseIfBlock = function(node) {
var inverse = node.inverse;
var firstItem = inverse && inverse.body[0];
// handle `{{else if foo}}`
if (inverse && firstItem && firstItem.type === 'BlockStatement') {
return inverse.loc.start.line === firstItem.loc.start.line &&
inverse.loc.start.column === firstItem.loc.start.column;
}
return false;
};
BlockIndentation.prototype.processNestedElseIfBlock = function(node) {
var elseBlockStatement = node.inverse.body[0];
elseBlockStatement._startOffset = 5 + elseBlockStatement.path.original.length;
};
BlockIndentation.prototype.startOffsetFor = function(node) {
return node._startOffset || 0;
};
BlockIndentation.prototype.shouldValidateBlockEnd = function(node) {
// HTML elements that start and end on the same line are fine
if (node.loc.start.line === node.loc.end.line) {
return;
}
// do not validate indentation on VOID_TAG's
if (VOID_TAGS[node.tag]) {
return;
}
var source = this.sourceForNode(node);
var endingToken = '/' + node.path.original;
var indexOfEnding = source.lastIndexOf(endingToken);
return indexOfEnding !== -1;
};
BlockIndentation.prototype.endingControlCharCount = function(node) {
if (node.type === 'ElementNode') {
// </>
return 3;
}
var source = this.sourceForNode(node);
var endingToken = '/' + node.path.original;
var indexOfEnding = source.lastIndexOf(endingToken);
if (indexOfEnding === -1) {
return 0;
}
var leadingControlCharCount = 0;
var i = indexOfEnding - 1;
while (isControlChar(source[i])) {
leadingControlCharCount++;
i--;
}
var trailingControlCharCount = 0;
i = indexOfEnding + endingToken.length;
while (isControlChar(source[i])) {
trailingControlCharCount++;
i++;
}
var closingSlash = 1;
return leadingControlCharCount + closingSlash + trailingControlCharCount;
};
function isControlChar(char) {
return char === '~' || char === '{' || char === '}';
}
BlockIndentation.prototype.sourceForNode = function(node) {
if (!node.loc) { return; }
var firstLine = node.loc.start.line - 1;
var lastLine = node.loc.end.line - 1;
var currentLine = firstLine - 1;
var firstColumn = node.loc.start.column;
var lastColumn = node.loc.end.column;
var string = [];
var line;
while (currentLine < lastLine) {
currentLine++;
line = this.source[currentLine];
if (currentLine === firstLine) {
if (firstLine === lastLine) {
string.push(line.slice(firstColumn, lastColumn));
} else {
string.push(line.slice(firstColumn));
}
} else if (currentLine === lastLine) {
string.push(line.slice(0, lastColumn));
} else {
string.push(line);
}
}
return string.join('\n');
};
// vendor stuff
$('script[type="text/x-handlebars"]').each(function() {
let template = $(this);
let templateName = template.attr('data-template-name') || template.attr('id') || 'application';
log('compiling');
try {
Ember.HTMLBars.compile(template.html(), {
rawSource: template.html(),
moduleName: templateName,
plugins: {
ast: [BlockIndentation]
}
});
} catch(e) {
log(e.stack)
}
logger('compiled: ', template.html())
});
// copied from emberjs/ember.js packages/ember-template-compiler/lib/system/calculate-location-display.js
function calculateLocationDisplay(moduleName, _loc) {
let loc = _loc || {};
let { column, line } = loc.start || {};
let moduleInfo = '';
if (moduleName) {
moduleInfo += `'${moduleName}'`;
}
if (line !== undefined && column !== undefined) {
if (moduleName) {
// only prepend @ if the moduleName was present
moduleInfo += '@ ';
}
moduleInfo += `L${line}:C${column}`;
}
if (moduleInfo) {
moduleInfo = `(${moduleInfo}) `;
}
return moduleInfo;
}
Ember.onerror = function(error) {
log(error.stack);
};
function log() {
var msg = [].slice.call(arguments).join(' ');
logs.insertBefore(document.createTextNode("\n" + msg), logs.firstChild);
}
Output
You can jump to the latest bin by adding /latest
to your URL
Keyboard Shortcuts
Shortcut | Action |
---|---|
ctrl + [num] | Toggle nth panel |
ctrl + 0 | Close focused panel |
ctrl + enter | Re-render output. If console visible: run JS in console |
Ctrl + l | Clear the console |
ctrl + / | Toggle comment on selected lines |
ctrl + ] | Indents selected lines |
ctrl + [ | Unindents selected lines |
tab | Code complete & Emmet expand |
ctrl + shift + L | Beautify code in active panel |
ctrl + s | Save & lock current Bin from further changes |
ctrl + shift + s | Open the share options |
ctrl + y | Archive Bin |
Complete list of JS Bin shortcuts |
JS Bin URLs
URL | Action |
---|---|
/ | Show the full rendered output. This content will update in real time as it's updated from the /edit url. |
/edit | Edit the current bin |
/watch | Follow a Code Casting session |
/embed | Create an embeddable version of the bin |
/latest | Load the very latest bin (/latest goes in place of the revision) |
/[username]/last | View the last edited bin for this user |
/[username]/last/edit | Edit the last edited bin for this user |
/[username]/last/watch | Follow the Code Casting session for the latest bin for this user |
/quiet | Remove analytics and edit button from rendered output |
.js | Load only the JavaScript for a bin |
.css | Load only the CSS for a bin |
Except for username prefixed urls, the url may start with http://jsbin.com/abc and the url fragments can be added to the url to view it differently. |