diff --git a/package-lock.json b/package-lock.json index 45d7819..8fae2be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "@typescript-eslint/parser": "^6.19.1", "@vscode/web-custom-data": "^0.4.9", "eslint": "^8.56.0", - "js-beautify": "^1.14.11", + "js-beautify": "^1.15.1", "mocha": "^10.2.0", "rimraf": "^5.0.5", "typescript": "^5.3.3" @@ -276,8 +276,7 @@ "version": "0.1.1", "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz", "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==", - "dev": true, - "license": "MIT" + "dev": true }, "node_modules/@pkgjs/parseargs": { "version": "0.11.0", @@ -801,7 +800,6 @@ "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", "dev": true, - "license": "MIT", "engines": { "node": ">=14" } @@ -818,7 +816,6 @@ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz", "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==", "dev": true, - "license": "MIT", "dependencies": { "ini": "^1.3.4", "proto-list": "~1.2.1" @@ -931,7 +928,6 @@ "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.4.tgz", "integrity": "sha512-L9Qe08KWTlqYMVvMcTIvMAdl1cDUubzRNYL+WfA4bLDMHe4nemKkpmYzkznE1FwLKu0EEmy6obgQKzMJrg4x9Q==", "dev": true, - "license": "MIT", "dependencies": { "@one-ini/wasm": "0.1.1", "commander": "^10.0.0", @@ -950,7 +946,6 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.1.tgz", "integrity": "sha512-0jWhJpD/MdhPXwPuiRkCbfYfSKp2qnn2eOc279qI7f+osl/l+prKSrvhg157zSYvx/1nmgn2NqdT6k2Z7zSH9w==", "dev": true, - "license": "ISC", "dependencies": { "brace-expansion": "^2.0.1" }, @@ -1494,8 +1489,7 @@ "version": "1.3.8", "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", - "dev": true, - "license": "ISC" + "dev": true }, "node_modules/is-binary-path": { "version": "2.1.0", @@ -1613,14 +1607,15 @@ } }, "node_modules/js-beautify": { - "version": "1.14.11", - "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.14.11.tgz", - "integrity": "sha512-rPogWqAfoYh1Ryqqh2agUpVfbxAhbjuN1SmU86dskQUKouRiggUTCO4+2ym9UPXllc2WAp0J+T5qxn7Um3lCdw==", + "version": "1.15.1", + "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.1.tgz", + "integrity": "sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==", "dev": true, "dependencies": { "config-chain": "^1.1.13", - "editorconfig": "^1.0.3", + "editorconfig": "^1.0.4", "glob": "^10.3.3", + "js-cookie": "^3.0.5", "nopt": "^7.2.0" }, "bin": { @@ -1669,6 +1664,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "dev": true, + "engines": { + "node": ">=14" + } + }, "node_modules/js-yaml": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", @@ -2131,9 +2135,8 @@ "node_modules/proto-list": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz", - "integrity": "sha1-IS1b/hMYMGpCD2QCuOJv85ZHqEk=", - "dev": true, - "license": "ISC" + "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==", + "dev": true }, "node_modules/punycode": { "version": "2.3.1", diff --git a/package.json b/package.json index 88a36de..88fb7e2 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "@typescript-eslint/parser": "^6.19.1", "@vscode/web-custom-data": "^0.4.9", "eslint": "^8.56.0", - "js-beautify": "^1.14.11", + "js-beautify": "^1.15.1", "mocha": "^10.2.0", "rimraf": "^5.0.5", "typescript": "^5.3.3" diff --git a/src/beautify/beautify-css.js b/src/beautify/beautify-css.js index 5983dd5..6bc5d92 100644 --- a/src/beautify/beautify-css.js +++ b/src/beautify/beautify-css.js @@ -1,5 +1,5 @@ // copied from js-beautify/js/lib/beautify-css.js -// version: 1.14.11 +// version: 1.15.1 /* AUTO-GENERATED. DO NOT MODIFY. */ /* @@ -573,10 +573,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'] - // For now, 'auto' = all off for javascript, all on for html (and inline javascript). + // valid templating languages ['django', 'erb', 'handlebars', 'php', 'smarty', 'angular'] + // For now, 'auto' = all off for javascript, all except angular on for html (and inline javascript/css). // 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', 'angular', 'django', 'erb', 'handlebars', 'php', 'smarty'], ['auto']); } Options.prototype._get_array = function(name, default_value) { diff --git a/src/beautify/beautify-html.d.ts b/src/beautify/beautify-html.d.ts index 84323ca..59f4940 100644 --- a/src/beautify/beautify-html.d.ts +++ b/src/beautify/beautify-html.d.ts @@ -113,10 +113,10 @@ export interface IBeautifyHTMLOptions { eol?: string; /** - * List of templating languages (auto,none,django,erb,handlebars,php) + * List of templating languages (auto,none,angular,django,erb,handlebars,php,smarty) * default ["auto"] = all in html */ - templating?: ('auto' | 'none' | 'django' | 'erb' | 'handlebars' | 'php')[]; + templating?: ('auto' | 'none' | 'angular' | 'django' | 'erb' | 'handlebars' | 'php' | 'smarty')[]; /** * Keep text content together between this string diff --git a/src/beautify/beautify-html.js b/src/beautify/beautify-html.js index 8d2a3d1..e0656c3 100644 --- a/src/beautify/beautify-html.js +++ b/src/beautify/beautify-html.js @@ -1,5 +1,5 @@ // copied from js-beautify/js/lib/beautify-html.js -// version: 1.14.11 +// version: 1.15.1 /* AUTO-GENERATED. DO NOT MODIFY. */ /* @@ -642,10 +642,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'] - // For now, 'auto' = all off for javascript, all on for html (and inline javascript). + // valid templating languages ['django', 'erb', 'handlebars', 'php', 'smarty', 'angular'] + // For now, 'auto' = all off for javascript, all except angular on for html (and inline javascript/css). // 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', 'angular', 'django', 'erb', 'handlebars', 'php', 'smarty'], ['auto']); } Options.prototype._get_array = function(name, default_value) { @@ -1518,7 +1518,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 @@ -1861,6 +1862,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) { @@ -2055,6 +2063,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); @@ -2069,6 +2081,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, @@ -2768,6 +2812,8 @@ var Pattern = (__webpack_require__(12).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', @@ -2792,11 +2838,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(/\@[a-zA-Z]+[^({]*[({]/), handlebars_comment: pattern_reader.starting_with(/{{!--/).until_after(/--}}/), handlebars: pattern_reader.starting_with(/{{/).until_after(/}}/), handlebars_open: pattern_reader.until(/[\n\r\t }]/), @@ -2810,6 +2858,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; @@ -2828,14 +2877,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() { @@ -2854,8 +2905,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); @@ -2920,7 +2972,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(); @@ -2937,7 +2989,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(); @@ -2952,11 +3004,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 "/>" @@ -3043,7 +3132,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]) { @@ -3052,7 +3141,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); diff --git a/src/beautify/esm/beautify-css.js b/src/beautify/esm/beautify-css.js index 745308b..7d5d1fe 100644 --- a/src/beautify/esm/beautify-css.js +++ b/src/beautify/esm/beautify-css.js @@ -1,5 +1,5 @@ // copied from js-beautify/js/lib/beautify-css.js -// version: 1.14.11 +// version: 1.15.1 /* AUTO-GENERATED. DO NOT MODIFY. */ /* @@ -570,10 +570,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'] - // For now, 'auto' = all off for javascript, all on for html (and inline javascript). + // valid templating languages ['django', 'erb', 'handlebars', 'php', 'smarty', 'angular'] + // For now, 'auto' = all off for javascript, all except angular on for html (and inline javascript/css). // 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', 'angular', 'django', 'erb', 'handlebars', 'php', 'smarty'], ['auto']); } Options.prototype._get_array = function(name, default_value) { diff --git a/src/beautify/esm/beautify-html.js b/src/beautify/esm/beautify-html.js index 7f5456c..5d33f34 100644 --- a/src/beautify/esm/beautify-html.js +++ b/src/beautify/esm/beautify-html.js @@ -1,5 +1,5 @@ // copied from js-beautify/js/lib/beautify-html.js -// version: 1.14.11 +// version: 1.15.1 /* AUTO-GENERATED. DO NOT MODIFY. */ /* @@ -642,10 +642,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'] - // For now, 'auto' = all off for javascript, all on for html (and inline javascript). + // valid templating languages ['django', 'erb', 'handlebars', 'php', 'smarty', 'angular'] + // For now, 'auto' = all off for javascript, all except angular on for html (and inline javascript/css). // 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', 'angular', 'django', 'erb', 'handlebars', 'php', 'smarty'], ['auto']); } Options.prototype._get_array = function(name, default_value) { @@ -1518,7 +1518,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 @@ -1861,6 +1862,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) { @@ -2055,6 +2063,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); @@ -2069,6 +2081,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, @@ -2768,6 +2812,8 @@ var Pattern = (__webpack_require__(12).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', @@ -2792,11 +2838,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(/\@[a-zA-Z]+[^({]*[({]/), handlebars_comment: pattern_reader.starting_with(/{{!--/).until_after(/--}}/), handlebars: pattern_reader.starting_with(/{{/).until_after(/}}/), handlebars_open: pattern_reader.until(/[\n\r\t }]/), @@ -2810,6 +2858,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; @@ -2828,14 +2877,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() { @@ -2854,8 +2905,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); @@ -2920,7 +2972,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(); @@ -2937,7 +2989,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(); @@ -2952,11 +3004,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 "/>" @@ -3043,7 +3132,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]) { @@ -3052,7 +3141,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); diff --git a/src/htmlLanguageTypes.ts b/src/htmlLanguageTypes.ts index a1a6d1b..db12026 100644 --- a/src/htmlLanguageTypes.ts +++ b/src/htmlLanguageTypes.ts @@ -44,7 +44,7 @@ export interface HTMLFormatConfiguration { endWithNewline?: boolean; extraLiners?: string; indentScripts?: 'keep' | 'separate' | 'normal'; - templating?: boolean; + templating?: ('auto' | 'none' | 'angular' | 'django' | 'erb' | 'handlebars' | 'php' | 'smarty')[] | boolean; unformattedContentDelimiter?: string; } diff --git a/src/services/htmlFormatter.ts b/src/services/htmlFormatter.ts index 360857b..37fd6b5 100644 --- a/src/services/htmlFormatter.ts +++ b/src/services/htmlFormatter.ts @@ -122,12 +122,15 @@ function getTagsFormatOption(options: HTMLFormatConfiguration, key: keyof HTMLFo return dflt; } -function getTemplatingFormatOption(options: HTMLFormatConfiguration, dflt: string): ('auto' | 'none' | 'django' | 'erb' | 'handlebars' | 'php')[] | undefined { +function getTemplatingFormatOption(options: HTMLFormatConfiguration, dflt: string): ('auto' | 'none' | 'angular' | 'django' | 'erb' | 'handlebars' | 'php' | 'smarty')[] | undefined { const value = getFormatOption(options, 'templating', dflt); if (value === true) { return ['auto']; } - return ['none']; + if (value === false || value === dflt || Array.isArray(value) === false) { + return ['none']; + } + return value; } function computeIndentLevel(content: string, offset: number, options: HTMLFormatConfiguration): number {