alignTransform.js 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", {
  3. value: true
  4. });
  5. exports.default = void 0;
  6. var _commentParser = require("comment-parser");
  7. /**
  8. * Transform based on https://github.com/syavorsky/comment-parser/blob/master/src/transforms/align.ts
  9. *
  10. * It contains some customizations to align based on the tags, and some custom options.
  11. */
  12. /**
  13. * @typedef {{
  14. * hasNoTypes: boolean,
  15. * maxNamedTagLength: import('./iterateJsdoc.js').Integer,
  16. * maxUnnamedTagLength: import('./iterateJsdoc.js').Integer
  17. * }} TypelessInfo
  18. */
  19. const {
  20. rewireSource
  21. } = _commentParser.util;
  22. /**
  23. * @typedef {{
  24. * name: import('./iterateJsdoc.js').Integer,
  25. * start: import('./iterateJsdoc.js').Integer,
  26. * tag: import('./iterateJsdoc.js').Integer,
  27. * type: import('./iterateJsdoc.js').Integer
  28. * }} Width
  29. */
  30. /** @type {Width} */
  31. const zeroWidth = {
  32. name: 0,
  33. start: 0,
  34. tag: 0,
  35. type: 0
  36. };
  37. /**
  38. * @param {string[]} tags
  39. * @param {import('./iterateJsdoc.js').Integer} index
  40. * @param {import('comment-parser').Line[]} source
  41. * @returns {boolean}
  42. */
  43. const shouldAlign = (tags, index, source) => {
  44. const tag = source[index].tokens.tag.replace('@', '');
  45. const includesTag = tags.includes(tag);
  46. if (includesTag) {
  47. return true;
  48. }
  49. if (tag !== '') {
  50. return false;
  51. }
  52. for (let iterator = index; iterator >= 0; iterator--) {
  53. const previousTag = source[iterator].tokens.tag.replace('@', '');
  54. if (previousTag !== '') {
  55. if (tags.includes(previousTag)) {
  56. return true;
  57. }
  58. return false;
  59. }
  60. }
  61. return true;
  62. };
  63. /**
  64. * @param {string[]} tags
  65. * @returns {(
  66. * width: Width,
  67. * line: {
  68. * tokens: import('comment-parser').Tokens
  69. * },
  70. * index: import('./iterateJsdoc.js').Integer,
  71. * source: import('comment-parser').Line[]
  72. * ) => Width}
  73. */
  74. const getWidth = tags => {
  75. return (width, {
  76. tokens
  77. }, index, source) => {
  78. if (!shouldAlign(tags, index, source)) {
  79. return width;
  80. }
  81. return {
  82. name: Math.max(width.name, tokens.name.length),
  83. start: tokens.delimiter === '/**' ? tokens.start.length : width.start,
  84. tag: Math.max(width.tag, tokens.tag.length),
  85. type: Math.max(width.type, tokens.type.length)
  86. };
  87. };
  88. };
  89. /**
  90. * @param {{
  91. * description: string;
  92. * tags: import('comment-parser').Spec[];
  93. * problems: import('comment-parser').Problem[];
  94. * }} fields
  95. * @returns {TypelessInfo}
  96. */
  97. const getTypelessInfo = fields => {
  98. const hasNoTypes = fields.tags.every(({
  99. type
  100. }) => {
  101. return !type;
  102. });
  103. const maxNamedTagLength = Math.max(...fields.tags.map(({
  104. tag,
  105. name
  106. }) => {
  107. return name.length === 0 ? -1 : tag.length;
  108. }).filter(length => {
  109. return length !== -1;
  110. })) + 1;
  111. const maxUnnamedTagLength = Math.max(...fields.tags.map(({
  112. tag,
  113. name
  114. }) => {
  115. return name.length === 0 ? tag.length : -1;
  116. }).filter(length => {
  117. return length !== -1;
  118. })) + 1;
  119. return {
  120. hasNoTypes,
  121. maxNamedTagLength,
  122. maxUnnamedTagLength
  123. };
  124. };
  125. /**
  126. * @param {import('./iterateJsdoc.js').Integer} len
  127. * @returns {string}
  128. */
  129. const space = len => {
  130. return ''.padStart(len, ' ');
  131. };
  132. /**
  133. * @param {{
  134. * customSpacings: import('../src/rules/checkLineAlignment.js').CustomSpacings,
  135. * tags: string[],
  136. * indent: string,
  137. * preserveMainDescriptionPostDelimiter: boolean,
  138. * wrapIndent: string,
  139. * }} cfg
  140. * @returns {(
  141. * block: import('comment-parser').Block
  142. * ) => import('comment-parser').Block}
  143. */
  144. const alignTransform = ({
  145. customSpacings,
  146. tags,
  147. indent,
  148. preserveMainDescriptionPostDelimiter,
  149. wrapIndent
  150. }) => {
  151. let intoTags = false;
  152. /** @type {Width} */
  153. let width;
  154. /**
  155. * @param {import('comment-parser').Tokens} tokens
  156. * @param {TypelessInfo} typelessInfo
  157. * @returns {import('comment-parser').Tokens}
  158. */
  159. const alignTokens = (tokens, typelessInfo) => {
  160. const nothingAfter = {
  161. delim: false,
  162. name: false,
  163. tag: false,
  164. type: false
  165. };
  166. if (tokens.description === '') {
  167. nothingAfter.name = true;
  168. tokens.postName = '';
  169. if (tokens.name === '') {
  170. nothingAfter.type = true;
  171. tokens.postType = '';
  172. if (tokens.type === '') {
  173. nothingAfter.tag = true;
  174. tokens.postTag = '';
  175. /* istanbul ignore next: Never happens because the !intoTags return. But it's here for consistency with the original align transform */
  176. if (tokens.tag === '') {
  177. nothingAfter.delim = true;
  178. }
  179. }
  180. }
  181. }
  182. let untypedNameAdjustment = 0;
  183. let untypedTypeAdjustment = 0;
  184. if (typelessInfo.hasNoTypes) {
  185. nothingAfter.tag = true;
  186. tokens.postTag = '';
  187. if (tokens.name === '') {
  188. untypedNameAdjustment = typelessInfo.maxNamedTagLength - tokens.tag.length;
  189. } else {
  190. untypedNameAdjustment = typelessInfo.maxNamedTagLength > typelessInfo.maxUnnamedTagLength ? 0 : Math.max(0, typelessInfo.maxUnnamedTagLength - (tokens.tag.length + tokens.name.length + 1));
  191. untypedTypeAdjustment = typelessInfo.maxNamedTagLength - tokens.tag.length;
  192. }
  193. }
  194. // Todo: Avoid fixing alignment of blocks with multiline wrapping of type
  195. if (tokens.tag === '' && tokens.type) {
  196. return tokens;
  197. }
  198. const spacings = {
  199. postDelimiter: (customSpacings === null || customSpacings === void 0 ? void 0 : customSpacings.postDelimiter) || 1,
  200. postName: (customSpacings === null || customSpacings === void 0 ? void 0 : customSpacings.postName) || 1,
  201. postTag: (customSpacings === null || customSpacings === void 0 ? void 0 : customSpacings.postTag) || 1,
  202. postType: (customSpacings === null || customSpacings === void 0 ? void 0 : customSpacings.postType) || 1
  203. };
  204. tokens.postDelimiter = nothingAfter.delim ? '' : space(spacings.postDelimiter);
  205. if (!nothingAfter.tag) {
  206. tokens.postTag = space(width.tag - tokens.tag.length + spacings.postTag);
  207. }
  208. if (!nothingAfter.type) {
  209. tokens.postType = space(width.type - tokens.type.length + spacings.postType + untypedTypeAdjustment);
  210. }
  211. if (!nothingAfter.name) {
  212. // If post name is empty for all lines (name width 0), don't add post name spacing.
  213. tokens.postName = width.name === 0 ? '' : space(width.name - tokens.name.length + spacings.postName + untypedNameAdjustment);
  214. }
  215. return tokens;
  216. };
  217. /**
  218. * @param {import('comment-parser').Line} line
  219. * @param {import('./iterateJsdoc.js').Integer} index
  220. * @param {import('comment-parser').Line[]} source
  221. * @param {TypelessInfo} typelessInfo
  222. * @param {string|false} indentTag
  223. * @returns {import('comment-parser').Line}
  224. */
  225. const update = (line, index, source, typelessInfo, indentTag) => {
  226. /** @type {import('comment-parser').Tokens} */
  227. const tokens = {
  228. ...line.tokens
  229. };
  230. if (tokens.tag !== '') {
  231. intoTags = true;
  232. }
  233. const isEmpty = tokens.tag === '' && tokens.name === '' && tokens.type === '' && tokens.description === '';
  234. // dangling '*/'
  235. if (tokens.end === '*/' && isEmpty) {
  236. tokens.start = indent + ' ';
  237. return {
  238. ...line,
  239. tokens
  240. };
  241. }
  242. switch (tokens.delimiter) {
  243. case '/**':
  244. tokens.start = indent;
  245. break;
  246. case '*':
  247. tokens.start = indent + ' ';
  248. break;
  249. default:
  250. tokens.delimiter = '';
  251. // compensate delimiter
  252. tokens.start = indent + ' ';
  253. }
  254. if (!intoTags) {
  255. if (tokens.description === '') {
  256. tokens.postDelimiter = '';
  257. } else if (!preserveMainDescriptionPostDelimiter) {
  258. tokens.postDelimiter = ' ';
  259. }
  260. return {
  261. ...line,
  262. tokens
  263. };
  264. }
  265. const postHyphenSpacing = (customSpacings === null || customSpacings === void 0 ? void 0 : customSpacings.postHyphen) ?? 1;
  266. const hyphenSpacing = /^\s*-\s+/u;
  267. tokens.description = tokens.description.replace(hyphenSpacing, '-' + ''.padStart(postHyphenSpacing, ' '));
  268. // Not align.
  269. if (shouldAlign(tags, index, source)) {
  270. alignTokens(tokens, typelessInfo);
  271. if (indentTag) {
  272. tokens.postDelimiter += wrapIndent;
  273. }
  274. }
  275. return {
  276. ...line,
  277. tokens
  278. };
  279. };
  280. return ({
  281. source,
  282. ...fields
  283. }) => {
  284. width = source.reduce(getWidth(tags), {
  285. ...zeroWidth
  286. });
  287. const typelessInfo = getTypelessInfo(fields);
  288. let tagIndentMode = false;
  289. return rewireSource({
  290. ...fields,
  291. source: source.map((line, index) => {
  292. const indentTag = tagIndentMode && !line.tokens.tag && line.tokens.description;
  293. const ret = update(line, index, source, typelessInfo, indentTag);
  294. if (line.tokens.tag) {
  295. tagIndentMode = true;
  296. }
  297. return ret;
  298. })
  299. });
  300. };
  301. };
  302. var _default = exports.default = alignTransform;
  303. module.exports = exports.default;
  304. //# sourceMappingURL=alignTransform.js.map