UNPKG

@gethue/sql-formatter

Version:

Format whitespace in a SQL query to make it more readable

302 lines (265 loc) 11.9 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports["default"] = void 0; var _tokenTypes = _interopRequireDefault(require("./tokenTypes")); var _Indentation = _interopRequireDefault(require("./Indentation")); var _InlineBlock = _interopRequireDefault(require("./InlineBlock")); var _Params = _interopRequireDefault(require("./Params")); var _utils = require("../utils"); var _token = require("./token"); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } function _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } function _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; } var Formatter = /*#__PURE__*/function () { /** * @param {Object} cfg * @param {String} cfg.language * @param {String} cfg.indent * @param {Boolean} cfg.uppercase * @param {Integer} cfg.linesBetweenQueries * @param {Boolean} cfg.indentQuerySeparator * @param {Object} cfg.params */ function Formatter(cfg) { _classCallCheck(this, Formatter); this.cfg = cfg; this.indentation = new _Indentation["default"](this.cfg.indent); this.inlineBlock = new _InlineBlock["default"](); this.params = new _Params["default"](this.cfg.params); this.previousReservedToken = {}; this.tokens = []; this.index = 0; } /** * SQL Tokenizer for this formatter, provided by subclasses. */ _createClass(Formatter, [{ key: "tokenizer", value: function tokenizer() { throw new Error('tokenizer() not implemented by subclass'); } /** * Reprocess and modify a token based on parsed context. * * @param {Object} token The token to modify * @param {String} token.type * @param {String} token.value * @return {Object} new token or the original * @return {String} token.type * @return {String} token.value */ }, { key: "tokenOverride", value: function tokenOverride(token) { // subclasses can override this to modify tokens during formatting return token; } /** * Formats whitespace in a SQL string to make it easier to read. * * @param {String} query The SQL query string * @return {String} formatted query */ }, { key: "format", value: function format(query) { this.tokens = this.tokenizer().tokenize(query); var formattedQuery = this.getFormattedQueryFromTokens(); return formattedQuery.trim(); } }, { key: "getFormattedQueryFromTokens", value: function getFormattedQueryFromTokens() { var _this = this; var formattedQuery = ''; this.tokens.forEach(function (token, index) { _this.index = index; token = _this.tokenOverride(token); if (token.type === _tokenTypes["default"].LINE_COMMENT) { formattedQuery = _this.formatLineComment(token, formattedQuery); } else if (token.type === _tokenTypes["default"].BLOCK_COMMENT) { formattedQuery = _this.formatBlockComment(token, formattedQuery); } else if (token.type === _tokenTypes["default"].RESERVED_TOP_LEVEL) { formattedQuery = _this.formatTopLevelReservedWord(token, formattedQuery); _this.previousReservedToken = token; } else if (token.type === _tokenTypes["default"].RESERVED_TOP_LEVEL_NO_INDENT) { formattedQuery = _this.formatTopLevelReservedWordNoIndent(token, formattedQuery); _this.previousReservedToken = token; } else if (token.type === _tokenTypes["default"].RESERVED_NEWLINE) { formattedQuery = _this.formatNewlineReservedWord(token, formattedQuery); _this.previousReservedToken = token; } else if (token.type === _tokenTypes["default"].RESERVED) { formattedQuery = _this.formatWithSpaces(token, formattedQuery); _this.previousReservedToken = token; } else if (token.type === _tokenTypes["default"].OPEN_PAREN) { formattedQuery = _this.formatOpeningParentheses(token, formattedQuery); } else if (token.type === _tokenTypes["default"].CLOSE_PAREN) { formattedQuery = _this.formatClosingParentheses(token, formattedQuery); } else if (token.type === _tokenTypes["default"].PLACEHOLDER) { formattedQuery = _this.formatPlaceholder(token, formattedQuery); } else if (token.value === ',') { formattedQuery = _this.formatComma(token, formattedQuery); } else if (token.value === ':') { formattedQuery = _this.formatWithSpaceAfter(token, formattedQuery); } else if (token.value === '.') { formattedQuery = _this.formatWithoutSpaces(token, formattedQuery); } else if (token.value === ';') { formattedQuery = _this.formatQuerySeparator(token, formattedQuery); } else { formattedQuery = _this.formatWithSpaces(token, formattedQuery); } }); return formattedQuery; } }, { key: "formatLineComment", value: function formatLineComment(token, query) { return this.addNewline(query + this.show(token)); } }, { key: "formatBlockComment", value: function formatBlockComment(token, query) { return this.addNewline(this.addNewline(query) + this.indentComment(token.value)); } }, { key: "indentComment", value: function indentComment(comment) { return comment.replace(/\n[\t ]*/g, '\n' + this.indentation.getIndent() + ' '); } }, { key: "formatTopLevelReservedWordNoIndent", value: function formatTopLevelReservedWordNoIndent(token, query) { this.indentation.decreaseTopLevel(); query = this.addNewline(query) + this.equalizeWhitespace(this.show(token)); return this.addNewline(query); } }, { key: "formatTopLevelReservedWord", value: function formatTopLevelReservedWord(token, query) { this.indentation.decreaseTopLevel(); query = this.addNewline(query); this.indentation.increaseTopLevel(); query += this.equalizeWhitespace(this.show(token)); return this.addNewline(query); } }, { key: "formatNewlineReservedWord", value: function formatNewlineReservedWord(token, query) { if ((0, _token.isAnd)(token) && (0, _token.isBetween)(this.tokenLookBehind(2))) { return this.formatWithSpaces(token, query); } return this.addNewline(query) + this.equalizeWhitespace(this.show(token)) + ' '; } // Replace any sequence of whitespace characters with single space }, { key: "equalizeWhitespace", value: function equalizeWhitespace(string) { return string.replace(/[\t-\r \xA0\u1680\u2000-\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF]+/g, ' '); } // Opening parentheses increase the block indent level and start a new line }, { key: "formatOpeningParentheses", value: function formatOpeningParentheses(token, query) { var _preserveWhitespaceFo, _this$tokenLookBehind; // Take out the preceding space unless there was whitespace there in the original query // or another opening parens or line comment var preserveWhitespaceFor = (_preserveWhitespaceFo = {}, _defineProperty(_preserveWhitespaceFo, _tokenTypes["default"].OPEN_PAREN, true), _defineProperty(_preserveWhitespaceFo, _tokenTypes["default"].LINE_COMMENT, true), _defineProperty(_preserveWhitespaceFo, _tokenTypes["default"].OPERATOR, true), _preserveWhitespaceFo); if (token.whitespaceBefore.length === 0 && !preserveWhitespaceFor[(_this$tokenLookBehind = this.tokenLookBehind()) === null || _this$tokenLookBehind === void 0 ? void 0 : _this$tokenLookBehind.type]) { query = (0, _utils.trimSpacesEnd)(query); } query += this.show(token); this.inlineBlock.beginIfPossible(this.tokens, this.index); if (!this.inlineBlock.isActive()) { this.indentation.increaseBlockLevel(); query = this.addNewline(query); } return query; } // Closing parentheses decrease the block indent level }, { key: "formatClosingParentheses", value: function formatClosingParentheses(token, query) { if (this.inlineBlock.isActive()) { this.inlineBlock.end(); return this.formatWithSpaceAfter(token, query); } else { this.indentation.decreaseBlockLevel(); return this.formatWithSpaces(token, this.addNewline(query)); } } }, { key: "formatPlaceholder", value: function formatPlaceholder(token, query) { return query + this.params.get(token) + ' '; } // Commas start a new line (unless within inline parentheses or SQL "LIMIT" clause) }, { key: "formatComma", value: function formatComma(token, query) { query = (0, _utils.trimSpacesEnd)(query) + this.show(token) + ' '; if (this.inlineBlock.isActive()) { return query; } else if ((0, _token.isLimit)(this.previousReservedToken)) { return query; } else { return this.addNewline(query); } } }, { key: "formatWithSpaceAfter", value: function formatWithSpaceAfter(token, query) { return (0, _utils.trimSpacesEnd)(query) + this.show(token) + ' '; } }, { key: "formatWithoutSpaces", value: function formatWithoutSpaces(token, query) { return (0, _utils.trimSpacesEnd)(query) + this.show(token); } }, { key: "formatWithSpaces", value: function formatWithSpaces(token, query) { return query + this.show(token) + ' '; } }, { key: "formatQuerySeparator", value: function formatQuerySeparator(token, query) { this.indentation.resetIndentation(); return (0, _utils.trimSpacesEnd)(query) + (this.cfg.indentQuerySeparator ? '\n' : '') + this.show(token) + '\n'.repeat(this.cfg.linesBetweenQueries || 1); } // Converts token to string (uppercasing it if needed) }, { key: "show", value: function show(_ref) { var type = _ref.type, value = _ref.value; if (this.cfg.uppercase && (type === _tokenTypes["default"].RESERVED || type === _tokenTypes["default"].RESERVED_TOP_LEVEL || type === _tokenTypes["default"].RESERVED_TOP_LEVEL_NO_INDENT || type === _tokenTypes["default"].RESERVED_NEWLINE || type === _tokenTypes["default"].OPEN_PAREN || type === _tokenTypes["default"].CLOSE_PAREN)) { return value.toUpperCase(); } else { return value; } } }, { key: "addNewline", value: function addNewline(query) { query = (0, _utils.trimSpacesEnd)(query); if (!query.endsWith('\n')) { query += '\n'; } return query + this.indentation.getIndent(); } }, { key: "tokenLookBehind", value: function tokenLookBehind() { var n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; return this.tokens[this.index - n]; } }, { key: "tokenLookAhead", value: function tokenLookAhead() { var n = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 1; return this.tokens[this.index + n]; } }]); return Formatter; }(); exports["default"] = Formatter; module.exports = exports.default;