format.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. /*---------------------------------------------------------------------------------------------
  2. * Copyright (c) Microsoft Corporation. All rights reserved.
  3. * Licensed under the MIT License. See License.txt in the project root for license information.
  4. *--------------------------------------------------------------------------------------------*/
  5. 'use strict';
  6. import { createScanner } from './scanner';
  7. import { cachedSpaces, cachedBreakLinesWithSpaces, supportedEols } from './string-intern';
  8. export function format(documentText, range, options) {
  9. let initialIndentLevel;
  10. let formatText;
  11. let formatTextStart;
  12. let rangeStart;
  13. let rangeEnd;
  14. if (range) {
  15. rangeStart = range.offset;
  16. rangeEnd = rangeStart + range.length;
  17. formatTextStart = rangeStart;
  18. while (formatTextStart > 0 && !isEOL(documentText, formatTextStart - 1)) {
  19. formatTextStart--;
  20. }
  21. let endOffset = rangeEnd;
  22. while (endOffset < documentText.length && !isEOL(documentText, endOffset)) {
  23. endOffset++;
  24. }
  25. formatText = documentText.substring(formatTextStart, endOffset);
  26. initialIndentLevel = computeIndentLevel(formatText, options);
  27. }
  28. else {
  29. formatText = documentText;
  30. initialIndentLevel = 0;
  31. formatTextStart = 0;
  32. rangeStart = 0;
  33. rangeEnd = documentText.length;
  34. }
  35. const eol = getEOL(options, documentText);
  36. const eolFastPathSupported = supportedEols.includes(eol);
  37. let numberLineBreaks = 0;
  38. let indentLevel = 0;
  39. let indentValue;
  40. if (options.insertSpaces) {
  41. indentValue = cachedSpaces[options.tabSize || 4] ?? repeat(cachedSpaces[1], options.tabSize || 4);
  42. }
  43. else {
  44. indentValue = '\t';
  45. }
  46. const indentType = indentValue === '\t' ? '\t' : ' ';
  47. let scanner = createScanner(formatText, false);
  48. let hasError = false;
  49. function newLinesAndIndent() {
  50. if (numberLineBreaks > 1) {
  51. return repeat(eol, numberLineBreaks) + repeat(indentValue, initialIndentLevel + indentLevel);
  52. }
  53. const amountOfSpaces = indentValue.length * (initialIndentLevel + indentLevel);
  54. if (!eolFastPathSupported || amountOfSpaces > cachedBreakLinesWithSpaces[indentType][eol].length) {
  55. return eol + repeat(indentValue, initialIndentLevel + indentLevel);
  56. }
  57. if (amountOfSpaces <= 0) {
  58. return eol;
  59. }
  60. return cachedBreakLinesWithSpaces[indentType][eol][amountOfSpaces];
  61. }
  62. function scanNext() {
  63. let token = scanner.scan();
  64. numberLineBreaks = 0;
  65. while (token === 15 /* SyntaxKind.Trivia */ || token === 14 /* SyntaxKind.LineBreakTrivia */) {
  66. if (token === 14 /* SyntaxKind.LineBreakTrivia */ && options.keepLines) {
  67. numberLineBreaks += 1;
  68. }
  69. else if (token === 14 /* SyntaxKind.LineBreakTrivia */) {
  70. numberLineBreaks = 1;
  71. }
  72. token = scanner.scan();
  73. }
  74. hasError = token === 16 /* SyntaxKind.Unknown */ || scanner.getTokenError() !== 0 /* ScanError.None */;
  75. return token;
  76. }
  77. const editOperations = [];
  78. function addEdit(text, startOffset, endOffset) {
  79. if (!hasError && (!range || (startOffset < rangeEnd && endOffset > rangeStart)) && documentText.substring(startOffset, endOffset) !== text) {
  80. editOperations.push({ offset: startOffset, length: endOffset - startOffset, content: text });
  81. }
  82. }
  83. let firstToken = scanNext();
  84. if (options.keepLines && numberLineBreaks > 0) {
  85. addEdit(repeat(eol, numberLineBreaks), 0, 0);
  86. }
  87. if (firstToken !== 17 /* SyntaxKind.EOF */) {
  88. let firstTokenStart = scanner.getTokenOffset() + formatTextStart;
  89. let initialIndent = (indentValue.length * initialIndentLevel < 20) && options.insertSpaces
  90. ? cachedSpaces[indentValue.length * initialIndentLevel]
  91. : repeat(indentValue, initialIndentLevel);
  92. addEdit(initialIndent, formatTextStart, firstTokenStart);
  93. }
  94. while (firstToken !== 17 /* SyntaxKind.EOF */) {
  95. let firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + formatTextStart;
  96. let secondToken = scanNext();
  97. let replaceContent = '';
  98. let needsLineBreak = false;
  99. while (numberLineBreaks === 0 && (secondToken === 12 /* SyntaxKind.LineCommentTrivia */ || secondToken === 13 /* SyntaxKind.BlockCommentTrivia */)) {
  100. let commentTokenStart = scanner.getTokenOffset() + formatTextStart;
  101. addEdit(cachedSpaces[1], firstTokenEnd, commentTokenStart);
  102. firstTokenEnd = scanner.getTokenOffset() + scanner.getTokenLength() + formatTextStart;
  103. needsLineBreak = secondToken === 12 /* SyntaxKind.LineCommentTrivia */;
  104. replaceContent = needsLineBreak ? newLinesAndIndent() : '';
  105. secondToken = scanNext();
  106. }
  107. if (secondToken === 2 /* SyntaxKind.CloseBraceToken */) {
  108. if (firstToken !== 1 /* SyntaxKind.OpenBraceToken */) {
  109. indentLevel--;
  110. }
  111. ;
  112. if (options.keepLines && numberLineBreaks > 0 || !options.keepLines && firstToken !== 1 /* SyntaxKind.OpenBraceToken */) {
  113. replaceContent = newLinesAndIndent();
  114. }
  115. else if (options.keepLines) {
  116. replaceContent = cachedSpaces[1];
  117. }
  118. }
  119. else if (secondToken === 4 /* SyntaxKind.CloseBracketToken */) {
  120. if (firstToken !== 3 /* SyntaxKind.OpenBracketToken */) {
  121. indentLevel--;
  122. }
  123. ;
  124. if (options.keepLines && numberLineBreaks > 0 || !options.keepLines && firstToken !== 3 /* SyntaxKind.OpenBracketToken */) {
  125. replaceContent = newLinesAndIndent();
  126. }
  127. else if (options.keepLines) {
  128. replaceContent = cachedSpaces[1];
  129. }
  130. }
  131. else {
  132. switch (firstToken) {
  133. case 3 /* SyntaxKind.OpenBracketToken */:
  134. case 1 /* SyntaxKind.OpenBraceToken */:
  135. indentLevel++;
  136. if (options.keepLines && numberLineBreaks > 0 || !options.keepLines) {
  137. replaceContent = newLinesAndIndent();
  138. }
  139. else {
  140. replaceContent = cachedSpaces[1];
  141. }
  142. break;
  143. case 5 /* SyntaxKind.CommaToken */:
  144. if (options.keepLines && numberLineBreaks > 0 || !options.keepLines) {
  145. replaceContent = newLinesAndIndent();
  146. }
  147. else {
  148. replaceContent = cachedSpaces[1];
  149. }
  150. break;
  151. case 12 /* SyntaxKind.LineCommentTrivia */:
  152. replaceContent = newLinesAndIndent();
  153. break;
  154. case 13 /* SyntaxKind.BlockCommentTrivia */:
  155. if (numberLineBreaks > 0) {
  156. replaceContent = newLinesAndIndent();
  157. }
  158. else if (!needsLineBreak) {
  159. replaceContent = cachedSpaces[1];
  160. }
  161. break;
  162. case 6 /* SyntaxKind.ColonToken */:
  163. if (options.keepLines && numberLineBreaks > 0) {
  164. replaceContent = newLinesAndIndent();
  165. }
  166. else if (!needsLineBreak) {
  167. replaceContent = cachedSpaces[1];
  168. }
  169. break;
  170. case 10 /* SyntaxKind.StringLiteral */:
  171. if (options.keepLines && numberLineBreaks > 0) {
  172. replaceContent = newLinesAndIndent();
  173. }
  174. else if (secondToken === 6 /* SyntaxKind.ColonToken */ && !needsLineBreak) {
  175. replaceContent = '';
  176. }
  177. break;
  178. case 7 /* SyntaxKind.NullKeyword */:
  179. case 8 /* SyntaxKind.TrueKeyword */:
  180. case 9 /* SyntaxKind.FalseKeyword */:
  181. case 11 /* SyntaxKind.NumericLiteral */:
  182. case 2 /* SyntaxKind.CloseBraceToken */:
  183. case 4 /* SyntaxKind.CloseBracketToken */:
  184. if (options.keepLines && numberLineBreaks > 0) {
  185. replaceContent = newLinesAndIndent();
  186. }
  187. else {
  188. if ((secondToken === 12 /* SyntaxKind.LineCommentTrivia */ || secondToken === 13 /* SyntaxKind.BlockCommentTrivia */) && !needsLineBreak) {
  189. replaceContent = cachedSpaces[1];
  190. }
  191. else if (secondToken !== 5 /* SyntaxKind.CommaToken */ && secondToken !== 17 /* SyntaxKind.EOF */) {
  192. hasError = true;
  193. }
  194. }
  195. break;
  196. case 16 /* SyntaxKind.Unknown */:
  197. hasError = true;
  198. break;
  199. }
  200. if (numberLineBreaks > 0 && (secondToken === 12 /* SyntaxKind.LineCommentTrivia */ || secondToken === 13 /* SyntaxKind.BlockCommentTrivia */)) {
  201. replaceContent = newLinesAndIndent();
  202. }
  203. }
  204. if (secondToken === 17 /* SyntaxKind.EOF */) {
  205. if (options.keepLines && numberLineBreaks > 0) {
  206. replaceContent = newLinesAndIndent();
  207. }
  208. else {
  209. replaceContent = options.insertFinalNewline ? eol : '';
  210. }
  211. }
  212. const secondTokenStart = scanner.getTokenOffset() + formatTextStart;
  213. addEdit(replaceContent, firstTokenEnd, secondTokenStart);
  214. firstToken = secondToken;
  215. }
  216. return editOperations;
  217. }
  218. function repeat(s, count) {
  219. let result = '';
  220. for (let i = 0; i < count; i++) {
  221. result += s;
  222. }
  223. return result;
  224. }
  225. function computeIndentLevel(content, options) {
  226. let i = 0;
  227. let nChars = 0;
  228. const tabSize = options.tabSize || 4;
  229. while (i < content.length) {
  230. let ch = content.charAt(i);
  231. if (ch === cachedSpaces[1]) {
  232. nChars++;
  233. }
  234. else if (ch === '\t') {
  235. nChars += tabSize;
  236. }
  237. else {
  238. break;
  239. }
  240. i++;
  241. }
  242. return Math.floor(nChars / tabSize);
  243. }
  244. function getEOL(options, text) {
  245. for (let i = 0; i < text.length; i++) {
  246. const ch = text.charAt(i);
  247. if (ch === '\r') {
  248. if (i + 1 < text.length && text.charAt(i + 1) === '\n') {
  249. return '\r\n';
  250. }
  251. return '\r';
  252. }
  253. else if (ch === '\n') {
  254. return '\n';
  255. }
  256. }
  257. return (options && options.eol) || '\n';
  258. }
  259. export function isEOL(text, offset) {
  260. return '\r\n'.indexOf(text.charAt(offset)) !== -1;
  261. }