Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed #2219 - formatting of new Angular control flow syntax #2221

Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@ Beautifier Options:
-C, --comma-first Put commas at the beginning of new line instead of end
-O, --operator-position Set operator position (before-newline|after-newline|preserve-newline) [before-newline]
--indent-empty-lines Keep indentation on empty lines
--templating List of templating languages (auto,django,erb,handlebars,php,smarty) ["auto"] auto = none in JavaScript, all in HTML
--templating List of templating languages (auto,django,erb,handlebars,php,smarty,angular) ["auto"] auto = none in JavaScript, all in HTML
```

Which correspond to the underscored option keys for both library interfaces
Expand Down Expand Up @@ -379,7 +379,7 @@ HTML Beautifier Options:
--indent_scripts Sets indent level inside script tags ("normal", "keep", "separate")
--unformatted_content_delimiter Keep text content together between this string [""]
--indent-empty-lines Keep indentation on empty lines
--templating List of templating languages (auto,none,django,erb,handlebars,php,smarty) ["auto"] auto = none in JavaScript, all in html
--templating List of templating languages (auto,none,django,erb,handlebars,php,smarty,angular) ["auto"] auto = none in JavaScript, all in html
```

## Directives
Expand Down
2 changes: 1 addition & 1 deletion js/src/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ function usage(err) {
' [first newline in file, otherwise "\\n]',
' -n, --end-with-newline End output with newline',
' --indent-empty-lines Keep indentation on empty lines',
' --templating List of templating languages (auto,none,django,erb,handlebars,php,smarty) ["auto"] auto = none in JavaScript, all in html',
' --templating List of templating languages (auto,none,django,erb,handlebars,php,smarty,angular) ["auto"] auto = none in JavaScript, all in html',
' --editorconfig Use EditorConfig to set up the options'
];

Expand Down
4 changes: 2 additions & 2 deletions js/src/core/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,10 @@ function Options(options, merge_child_field) {

this.indent_empty_lines = this._get_boolean('indent_empty_lines');

// valid templating languages ['django', 'erb', 'handlebars', 'php', 'smarty']
// valid templating languages ['django', 'erb', 'handlebars', 'php', 'smarty', 'angular']
// For now, 'auto' = all off for javascript, all on for html (and inline javascript).
// other values ignored
this.templating = this._get_selection_list('templating', ['auto', 'none', 'django', 'erb', 'handlebars', 'php', 'smarty'], ['auto']);
this.templating = this._get_selection_list('templating', ['auto', 'none', 'django', 'erb', 'handlebars', 'php', 'smarty', 'angular'], ['auto']);
}

Options.prototype._get_array = function(name, default_value) {
Expand Down
3 changes: 2 additions & 1 deletion js/src/core/templatablepattern.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ var template_names = {
erb: false,
handlebars: false,
php: false,
smarty: false
smarty: false,
angular: false
};

// This lets templates appear anywhere we would do a readUntil
Expand Down
43 changes: 43 additions & 0 deletions js/src/html/beautifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,13 @@ Printer.prototype.indent = function() {
this.indent_level++;
};

Printer.prototype.deindent = function() {
if (this.indent_level > 0) {
this.indent_level--;
this._output.set_indent(this.indent_level, this.alignment_size);
}
};

Printer.prototype.get_full_indent = function(level) {
level = this.indent_level + (level || 0);
if (level < 1) {
Expand Down Expand Up @@ -305,6 +312,10 @@ Beautifier.prototype.beautify = function() {
parser_token = this._handle_tag_close(printer, raw_token, last_tag_token);
} else if (raw_token.type === TOKEN.TEXT) {
parser_token = this._handle_text(printer, raw_token, last_tag_token);
} else if (raw_token.type === TOKEN.CONTROL_FLOW_OPEN) {
parser_token = this._handle_control_flow_open(printer, raw_token);
} else if (raw_token.type === TOKEN.CONTROL_FLOW_CLOSE) {
parser_token = this._handle_control_flow_close(printer, raw_token);
} else {
// This should never happen, but if it does. Print the raw token
printer.add_raw_token(raw_token);
Expand All @@ -319,6 +330,38 @@ Beautifier.prototype.beautify = function() {
return sweet_code;
};

Beautifier.prototype._handle_control_flow_open = function(printer, raw_token) {
var parser_token = {
text: raw_token.text,
type: raw_token.type
};
printer.set_space_before_token(raw_token.newlines || raw_token.whitespace_before !== '', true);
if (raw_token.newlines) {
printer.print_preserved_newlines(raw_token);
} else {
printer.set_space_before_token(raw_token.newlines || raw_token.whitespace_before !== '', true);
}
printer.print_token(raw_token);
printer.indent();
return parser_token;
};

Beautifier.prototype._handle_control_flow_close = function(printer, raw_token) {
var parser_token = {
text: raw_token.text,
type: raw_token.type
};

printer.deindent();
if (raw_token.newlines) {
printer.print_preserved_newlines(raw_token);
} else {
printer.set_space_before_token(raw_token.newlines || raw_token.whitespace_before !== '', true);
}
printer.print_token(raw_token);
return parser_token;
};

Beautifier.prototype._handle_tag_close = function(printer, raw_token, last_tag_token) {
var parser_token = {
text: raw_token.text,
Expand Down
2 changes: 1 addition & 1 deletion js/src/html/options.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ var BaseOptions = require('../core/options').Options;
function Options(options) {
BaseOptions.call(this, options, 'html');
if (this.templating.length === 1 && this.templating[0] === 'auto') {
this.templating = ['django', 'erb', 'handlebars', 'php'];
this.templating = ['django', 'erb', 'handlebars', 'php', 'angular'];
}

this.indent_inner_html = this._get_boolean('indent_inner_html');
Expand Down
63 changes: 54 additions & 9 deletions js/src/html/tokenizer.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ var Pattern = require('../core/pattern').Pattern;
var TOKEN = {
TAG_OPEN: 'TK_TAG_OPEN',
TAG_CLOSE: 'TK_TAG_CLOSE',
CONTROL_FLOW_OPEN: 'TK_CONTROL_FLOW_OPEN',
CONTROL_FLOW_CLOSE: 'TK_CONTROL_FLOW_CLOSE',
ATTRIBUTE: 'TK_ATTRIBUTE',
EQUALS: 'TK_EQUALS',
VALUE: 'TK_VALUE',
Expand All @@ -61,11 +63,13 @@ var Tokenizer = function(input_string, options) {

this.__patterns = {
word: templatable_reader.until(/[\n\r\t <]/),
word_control_flow_close_excluded: templatable_reader.until(/[\n\r\t <}]/),
single_quote: templatable_reader.until_after(/'/),
double_quote: templatable_reader.until_after(/"/),
attribute: templatable_reader.until(/[\n\r\t =>]|\/>/),
element_name: templatable_reader.until(/[\n\r\t >\/]/),

angular_control_flow_start: pattern_reader.matching(/\@[^\n\t ][^({]*[({]/),
bitwiseman marked this conversation as resolved.
Show resolved Hide resolved
handlebars_comment: pattern_reader.starting_with(/{{!--/).until_after(/--}}/),
handlebars: pattern_reader.starting_with(/{{/).until_after(/}}/),
handlebars_open: pattern_reader.until(/[\n\r\t }]/),
Expand All @@ -79,6 +83,7 @@ var Tokenizer = function(input_string, options) {

if (this._options.indent_handlebars) {
this.__patterns.word = this.__patterns.word.exclude('handlebars');
this.__patterns.word_control_flow_close_excluded = this.__patterns.word_control_flow_close_excluded.exclude('handlebars');
}

this._unformatted_content_delimiter = null;
Expand All @@ -97,14 +102,16 @@ Tokenizer.prototype._is_comment = function(current_token) { // jshint unused:fal
};

Tokenizer.prototype._is_opening = function(current_token) {
return current_token.type === TOKEN.TAG_OPEN;
return current_token.type === TOKEN.TAG_OPEN || current_token.type === TOKEN.CONTROL_FLOW_OPEN;
};

Tokenizer.prototype._is_closing = function(current_token, open_token) {
return current_token.type === TOKEN.TAG_CLOSE &&
return (current_token.type === TOKEN.TAG_CLOSE &&
(open_token && (
((current_token.text === '>' || current_token.text === '/>') && open_token.text[0] === '<') ||
(current_token.text === '}}' && open_token.text[0] === '{' && open_token.text[1] === '{')));
(current_token.text === '}}' && open_token.text[0] === '{' && open_token.text[1] === '{')))
) || (current_token.type === TOKEN.CONTROL_FLOW_CLOSE &&
(current_token.text === '}' && open_token.text.endsWith('{')));
};

Tokenizer.prototype._reset = function() {
Expand All @@ -123,8 +130,9 @@ Tokenizer.prototype._get_next_token = function(previous_token, open_token) { //
token = token || this._read_open_handlebars(c, open_token);
token = token || this._read_attribute(c, previous_token, open_token);
token = token || this._read_close(c, open_token);
token = token || this._read_control_flows(c, open_token);
token = token || this._read_raw_content(c, previous_token, open_token);
token = token || this._read_content_word(c);
token = token || this._read_content_word(c, open_token);
token = token || this._read_comment_or_cdata(c);
token = token || this._read_processing(c);
token = token || this._read_open(c, open_token);
Expand Down Expand Up @@ -189,7 +197,7 @@ Tokenizer.prototype._read_processing = function(c) { // jshint unused:false
Tokenizer.prototype._read_open = function(c, open_token) {
var resulting_string = null;
var token = null;
if (!open_token) {
if (!open_token || open_token.type === TOKEN.CONTROL_FLOW_OPEN) {
if (c === '<') {

resulting_string = this._input.next();
Expand All @@ -206,7 +214,7 @@ Tokenizer.prototype._read_open = function(c, open_token) {
Tokenizer.prototype._read_open_handlebars = function(c, open_token) {
var resulting_string = null;
var token = null;
if (!open_token) {
if (!open_token || open_token.type === TOKEN.CONTROL_FLOW_OPEN) {
if (this._options.indent_handlebars && c === '{' && this._input.peek(1) === '{') {
if (this._input.peek(2) === '!') {
resulting_string = this.__patterns.handlebars_comment.read();
Expand All @@ -221,11 +229,48 @@ Tokenizer.prototype._read_open_handlebars = function(c, open_token) {
return token;
};

Tokenizer.prototype._read_control_flows = function(c, open_token) {
var resulting_string = '';
var token = null;
// Only check for control flows if angular templating is set AND indenting is set
if (!this._options.templating.includes('angular') || !this._options.indent_handlebars) {
return token;
}

if (c === '@') {
resulting_string = this.__patterns.angular_control_flow_start.read();
if (resulting_string === '') {
return token;
}

var opening_parentheses_count = resulting_string.endsWith('(') ? 1 : 0;
var closing_parentheses_count = 0;
// The opening brace of the control flow is where the number of opening and closing parentheses equal
// e.g. @if({value: true} !== null) {
while (!(resulting_string.endsWith('{') && opening_parentheses_count === closing_parentheses_count)) {
var next_char = this._input.next();
if (next_char === null) {
break;
} else if (next_char === '(') {
opening_parentheses_count++;
} else if (next_char === ')') {
closing_parentheses_count++;
}
resulting_string += next_char;
}
token = this._create_token(TOKEN.CONTROL_FLOW_OPEN, resulting_string);
} else if (c === '}' && open_token && open_token.type === TOKEN.CONTROL_FLOW_OPEN) {
resulting_string = this._input.next();
token = this._create_token(TOKEN.CONTROL_FLOW_CLOSE, resulting_string);
}
return token;
};


Tokenizer.prototype._read_close = function(c, open_token) {
var resulting_string = null;
var token = null;
if (open_token) {
if (open_token && open_token.type === TOKEN.TAG_OPEN) {
if (open_token.text[0] === '<' && (c === '>' || (c === '/' && this._input.peek(1) === '>'))) {
resulting_string = this._input.next();
if (c === '/') { // for close tag "/>"
Expand Down Expand Up @@ -312,7 +357,7 @@ Tokenizer.prototype._read_raw_content = function(c, previous_token, open_token)
return null;
};

Tokenizer.prototype._read_content_word = function(c) {
Tokenizer.prototype._read_content_word = function(c, open_token) {
var resulting_string = '';
if (this._options.unformatted_content_delimiter) {
if (c === this._options.unformatted_content_delimiter[0]) {
Expand All @@ -321,7 +366,7 @@ Tokenizer.prototype._read_content_word = function(c) {
}

if (!resulting_string) {
resulting_string = this.__patterns.word.read();
resulting_string = (open_token && open_token.type === TOKEN.CONTROL_FLOW_OPEN) ? this.__patterns.word_control_flow_close_excluded.read() : this.__patterns.word.read();
}
if (resulting_string) {
return this._create_token(TOKEN.TEXT, resulting_string);
Expand Down
2 changes: 1 addition & 1 deletion python/jsbeautifier/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ def usage(stream=sys.stdout):
NOTE: Line continues until next wrap point is found.
-n, --end-with-newline End output with newline
--indent-empty-lines Keep indentation on empty lines
--templating List of templating languages (auto,none,django,erb,handlebars,php,smarty) ["auto"] auto = none in JavaScript, all in html
--templating List of templating languages (auto,none,django,erb,handlebars,php,smarty,angular) ["auto"] auto = none in JavaScript, all in html
--editorconfig Enable setting configuration from EditorConfig

Rarely needed options:
Expand Down
4 changes: 2 additions & 2 deletions python/jsbeautifier/core/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,12 @@ def __init__(self, options=None, merge_child_field=None):

self.indent_empty_lines = self._get_boolean("indent_empty_lines")

# valid templating languages ['django', 'erb', 'handlebars', 'php', 'smarty']
# valid templating languages ['django', 'erb', 'handlebars', 'php', 'smarty', 'angular']
# For now, 'auto' = all off for javascript, all on for html (and inline javascript).
# other values ignored
self.templating = self._get_selection_list(
"templating",
["auto", "none", "django", "erb", "handlebars", "php", "smarty"],
["auto", "none", "django", "erb", "handlebars", "php", "smarty", "angular"],
["auto"],
)

Expand Down
3 changes: 2 additions & 1 deletion python/jsbeautifier/core/templatablepattern.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def __init__(self):
self.handlebars = False
self.php = False
self.smarty = False
self.angular = False


class TemplatePatterns:
Expand Down Expand Up @@ -78,7 +79,7 @@ def _update(self):

def read_options(self, options):
result = self._create()
for language in ["django", "erb", "handlebars", "php", "smarty"]:
for language in ["django", "erb", "handlebars", "php", "smarty", "angular"]:
setattr(result._disabled, language, not (language in options.templating))
result._update()
return result
Expand Down
Loading
Loading