From f059d7a31bf6db6f82877b00767e43667e195268 Mon Sep 17 00:00:00 2001 From: Shinigami Date: Tue, 5 Nov 2019 12:28:44 +0100 Subject: [PATCH] JSDoc type added in `src/common/util.js` (#6788) * JSDoc type added in `src/common/util.js` * skip can return false * Add TBD for false - 1 not allowed * Define typedef SkipOptions * Use method generic for node param * Define node param for setLocStart and setLocEnd * Copy idx param to local variable * Add @returns false * Update Utility functions docs * hasNewline param index: allow only number * Add condition * Add function keyword to docs * Add prettier-ignore * Improve condition * Remove TBD comment * Use single quotes * Add type to oldIndex * Use singleline JSDocs --- docs/plugins.md | 45 ++--- src/common/util.js | 161 +++++++++++++++++- src/main/comments.js | 3 +- .../versioned_docs/version-stable/plugins.md | 45 ++--- 4 files changed, 203 insertions(+), 51 deletions(-) diff --git a/docs/plugins.md b/docs/plugins.md index c5dd2641..ceee4982 100644 --- a/docs/plugins.md +++ b/docs/plugins.md @@ -228,28 +228,31 @@ defaultOptions: { A `util` module from Prettier core is considered a private API and is not meant to be consumed by plugins. Instead, the `util-shared` module provides the following limited set of utility functions for plugins: + ```ts -getMaxContinuousCount(str: string, target: string): number; -getStringWidth(text: string): number; -getAlignmentSize(value: string, tabWidth: number, startIndex: number): number; -getIndentSize(value: string, tabWidth: number): number; -skip(chars: string|RegExp): number; -skipWhitespace(text: string, index: number, options: object): number; -skipSpaces(text: string, index: number, options: object): number; -skipToLineEnd(text: string, index: number, options: object): number; -skipEverythingButNewLine(text: string, index: number, options: object): number; -skipInlineComment(text: string, index: number): number; -skipTrailingComment(text: string, index: number): number; -skipNewline(text: string, index: number, options: object): number; -hasNewline(text: string, index: number, options: object): boolean; -hasNewlineInRange(text: string, start: number, start: number): boolean; -hasSpaces(text: string, index: number, options: object): number; -makeString(rawContent: string, enclosingQuote: string, unescapeUnnecessaryEscapes: boolean): string; -getNextNonSpaceNonCommentCharacterIndex(text: string, node: object, options: object): number; -isNextLineEmptyAfterIndex(text: string, index: number): boolean; -isNextLineEmpty(text: string, node: object, options: object): boolean; -isPreviousLineEmpty(text: string, node: object, options: object): boolean; -mapDoc(doc: object, callback: function): void; +type Quote = '"' | "'"; +type SkipOptions = { backwards?: boolean }; +function getMaxContinuousCount(str: string, target: string): number; +function getStringWidth(text: string): number; +function getAlignmentSize(value: string, tabWidth: number, startIndex?: number): number; +function getIndentSize(value: string, tabWidth: number): number; +function skip(chars: string | RegExp): (text: string, index: number | false, opts?: SkipOptions) => number | false; +function skipWhitespace(text: string, index: number | false, opts?: SkipOptions): number | false; +function skipSpaces(text: string, index: number | false, opts?: SkipOptions): number | false; +function skipToLineEnd(text: string, index: number | false, opts?: SkipOptions): number | false; +function skipEverythingButNewLine(text: string, index: number | false, opts?: SkipOptions): number | false; +function skipInlineComment(text: string, index: number | false): number | false; +function skipTrailingComment(text: string, index: number | false): number | false; +function skipNewline(text: string, index: number | false, opts?: SkipOptions): number | false; +function hasNewline(text: string, index: number, opts?: SkipOptions): boolean; +function hasNewlineInRange(text: string, start: number, end: number): boolean; +function hasSpaces(text: string, index: number, opts?: SkipOptions): boolean; +function makeString(rawContent: string, enclosingQuote: Quote, unescapeUnnecessaryEscapes?: boolean): string; +function getNextNonSpaceNonCommentCharacterIndex(text: string, node: N, locEnd: (node: N) => number): number | false; +function isNextLineEmptyAfterIndex(text: string, index: number): boolean; +function isNextLineEmpty(text: string, node: N, locEnd: (node: N) => number): boolean; +function isPreviousLineEmpty(text: string, node: N, locStart: (node: N) => number): boolean; +function mapDoc(doc: object, callback: function): void; ``` ### Tutorials diff --git a/src/common/util.js b/src/common/util.js index 582f76ee..9f13f83c 100644 --- a/src/common/util.js +++ b/src/common/util.js @@ -38,6 +38,14 @@ function getPenultimate(arr) { return null; } +/** + * @typedef {{backwards?: boolean}} SkipOptions + */ + +/** + * @param {string | RegExp} chars + * @returns {(text: string, index: number | false, opts?: SkipOptions) => number | false} + */ function skip(chars) { return (text, index, opts) => { const backwards = opts && opts.backwards; @@ -74,11 +82,28 @@ function skip(chars) { }; } +/** + * @type {(text: string, index: number | false, opts?: SkipOptions) => number | false} + */ const skipWhitespace = skip(/\s/); +/** + * @type {(text: string, index: number | false, opts?: SkipOptions) => number | false} + */ const skipSpaces = skip(" \t"); +/** + * @type {(text: string, index: number | false, opts?: SkipOptions) => number | false} + */ const skipToLineEnd = skip(",; \t"); +/** + * @type {(text: string, index: number | false, opts?: SkipOptions) => number | false} + */ const skipEverythingButNewLine = skip(/[^\r\n]/); +/** + * @param {string} text + * @param {number | false} index + * @returns {number | false} + */ function skipInlineComment(text, index) { if (index === false) { return false; @@ -94,6 +119,11 @@ function skipInlineComment(text, index) { return index; } +/** + * @param {string} text + * @param {number | false} index + * @returns {number | false} + */ function skipTrailingComment(text, index) { if (index === false) { return false; @@ -108,6 +138,12 @@ function skipTrailingComment(text, index) { // This one doesn't use the above helper function because it wants to // test \r\n in order and `skip` doesn't support ordering and we only // want to skip one newline. It's simple to implement. +/** + * @param {string} text + * @param {number | false} index + * @param {SkipOptions=} opts + * @returns {number | false} + */ function skipNewline(text, index, opts) { const backwards = opts && opts.backwards; if (index === false) { @@ -144,6 +180,12 @@ function skipNewline(text, index, opts) { return index; } +/** + * @param {string} text + * @param {number} index + * @param {SkipOptions=} opts + * @returns {boolean} + */ function hasNewline(text, index, opts) { opts = opts || {}; const idx = skipSpaces(text, opts.backwards ? index - 1 : index, opts); @@ -151,6 +193,12 @@ function hasNewline(text, index, opts) { return idx !== idx2; } +/** + * @param {string} text + * @param {number} start + * @param {number} end + * @returns {boolean} + */ function hasNewlineInRange(text, start, end) { for (let i = start; i < end; ++i) { if (text.charAt(i) === "\n") { @@ -161,7 +209,14 @@ function hasNewlineInRange(text, start, end) { } // Note: this function doesn't ignore leading comments unlike isNextLineEmpty +/** + * @template N + * @param {string} text + * @param {N} node + * @param {(node: N) => number} locStart + */ function isPreviousLineEmpty(text, node, locStart) { + /** @type {number | false} */ let idx = locStart(node) - 1; idx = skipSpaces(text, idx, { backwards: true }); idx = skipNewline(text, idx, { backwards: true }); @@ -170,8 +225,15 @@ function isPreviousLineEmpty(text, node, locStart) { return idx !== idx2; } +/** + * @param {string} text + * @param {number} index + * @returns {boolean} + */ function isNextLineEmptyAfterIndex(text, index) { + /** @type {number | false} */ let oldIdx = null; + /** @type {number | false} */ let idx = index; while (idx !== oldIdx) { // We need to skip all the potential trailing inline comments @@ -182,25 +244,47 @@ function isNextLineEmptyAfterIndex(text, index) { } idx = skipTrailingComment(text, idx); idx = skipNewline(text, idx); - return hasNewline(text, idx); + return idx !== false && hasNewline(text, idx); } +/** + * @template N + * @param {string} text + * @param {N} node + * @param {(node: N) => number} locEnd + * @returns {boolean} + */ function isNextLineEmpty(text, node, locEnd) { return isNextLineEmptyAfterIndex(text, locEnd(node)); } +/** + * @param {string} text + * @param {number} idx + * @returns {number | false} + */ function getNextNonSpaceNonCommentCharacterIndexWithStartIndex(text, idx) { + /** @type {number | false} */ let oldIdx = null; - while (idx !== oldIdx) { - oldIdx = idx; - idx = skipSpaces(text, idx); - idx = skipInlineComment(text, idx); - idx = skipTrailingComment(text, idx); - idx = skipNewline(text, idx); + /** @type {number | false} */ + let nextIdx = idx; + while (nextIdx !== oldIdx) { + oldIdx = nextIdx; + nextIdx = skipSpaces(text, nextIdx); + nextIdx = skipInlineComment(text, nextIdx); + nextIdx = skipTrailingComment(text, nextIdx); + nextIdx = skipNewline(text, nextIdx); } - return idx; + return nextIdx; } +/** + * @template N + * @param {string} text + * @param {N} node + * @param {(node: N) => number} locEnd + * @returns {number | false} + */ function getNextNonSpaceNonCommentCharacterIndex(text, node, locEnd) { return getNextNonSpaceNonCommentCharacterIndexWithStartIndex( text, @@ -208,18 +292,36 @@ function getNextNonSpaceNonCommentCharacterIndex(text, node, locEnd) { ); } +/** + * @template N + * @param {string} text + * @param {N} node + * @param {(node: N) => number} locEnd + * @returns {string} + */ function getNextNonSpaceNonCommentCharacter(text, node, locEnd) { return text.charAt( + // @ts-ignore => TBD: can return false, should we define a fallback? getNextNonSpaceNonCommentCharacterIndex(text, node, locEnd) ); } +/** + * @param {string} text + * @param {number} index + * @param {SkipOptions=} opts + * @returns {boolean} + */ function hasSpaces(text, index, opts) { opts = opts || {}; const idx = skipSpaces(text, opts.backwards ? index - 1 : index, opts); return idx !== index; } +/** + * @param {{range?: [number, number], start?: number}} node + * @param {number} index + */ function setLocStart(node, index) { if (node.range) { node.range[0] = index; @@ -228,6 +330,10 @@ function setLocStart(node, index) { } } +/** + * @param {{range?: [number, number], end?: number}} node + * @param {number} index + */ function setLocEnd(node, index) { if (node.range) { node.range[1] = index; @@ -400,6 +506,12 @@ function getLeftMost(node) { return node; } +/** + * @param {string} value + * @param {number} tabWidth + * @param {number=} startIndex + * @returns {number} + */ function getAlignmentSize(value, tabWidth, startIndex) { startIndex = startIndex || 0; @@ -419,6 +531,11 @@ function getAlignmentSize(value, tabWidth, startIndex) { return size; } +/** + * @param {string} value + * @param {number} tabWidth + * @returns {number} + */ function getIndentSize(value, tabWidth) { const lastNewlineIndex = value.lastIndexOf("\n"); if (lastNewlineIndex === -1) { @@ -432,12 +549,24 @@ function getIndentSize(value, tabWidth) { ); } +/** + * @typedef {'"' | "'"} Quote + */ + +/** + * + * @param {string} raw + * @param {Quote} preferredQuote + * @returns {Quote} + */ function getPreferredQuote(raw, preferredQuote) { // `rawContent` is the string exactly like it appeared in the input source // code, without its enclosing quotes. const rawContent = raw.slice(1, -1); + /** @type {{ quote: '"', regex: RegExp }} */ const double = { quote: '"', regex: /"/g }; + /** @type {{ quote: "'", regex: RegExp }} */ const single = { quote: "'", regex: /'/g }; const preferred = preferredQuote === "'" ? single : double; @@ -474,6 +603,7 @@ function printString(raw, options, isDirectiveLiteral) { const canChangeDirectiveQuotes = !rawContent.includes('"') && !rawContent.includes("'"); + /** @type {Quote} */ const enclosingQuote = options.parser === "json" ? '"' @@ -508,6 +638,12 @@ function printString(raw, options, isDirectiveLiteral) { ); } +/** + * @param {string} rawContent + * @param {Quote} enclosingQuote + * @param {boolean=} unescapeUnnecessaryEscapes + * @returns {string} + */ function makeString(rawContent, enclosingQuote, unescapeUnnecessaryEscapes) { const otherQuote = enclosingQuote === '"' ? "'" : '"'; @@ -563,6 +699,11 @@ function printNumber(rawNumber) { ); } +/** + * @param {string} str + * @param {string} target + * @returns {number} + */ function getMaxContinuousCount(str, target) { const results = str.match( new RegExp(`(${escapeStringRegexp(target)})+`, "g") @@ -607,6 +748,10 @@ function getMinNotPresentContinuousCount(str, target) { return max + 1; } +/** + * @param {string} text + * @returns {number} + */ function getStringWidth(text) { if (!text) { return 0; diff --git a/src/main/comments.js b/src/main/comments.js index 895dce48..84e9dc28 100644 --- a/src/main/comments.js +++ b/src/main/comments.js @@ -510,7 +510,8 @@ function printComments(path, print, options, needsSemi) { leadingParts.push(contents); const text = options.originalText; - if (hasNewline(text, skipNewline(text, options.locEnd(comment)))) { + const index = skipNewline(text, options.locEnd(comment)); + if (index !== false && hasNewline(text, index)) { leadingParts.push(hardline); } } else if (trailing) { diff --git a/website/versioned_docs/version-stable/plugins.md b/website/versioned_docs/version-stable/plugins.md index c1682891..24c654c3 100644 --- a/website/versioned_docs/version-stable/plugins.md +++ b/website/versioned_docs/version-stable/plugins.md @@ -228,28 +228,31 @@ defaultOptions: { A `util` module from Prettier core is considered a private API and is not meant to be consumed by plugins. Instead, the `util-shared` module provides the following limited set of utility functions for plugins: + ```ts -getMaxContinuousCount(str: string, target: string): number; -getStringWidth(text: string): number; -getAlignmentSize(value: string, tabWidth: number, startIndex: number): number; -getIndentSize(value: string, tabWidth: number): number; -skip(chars: string|RegExp): number; -skipWhitespace(text: string, index: number, options: object): number; -skipSpaces(text: string, index: number, options: object): number; -skipToLineEnd(text: string, index: number, options: object): number; -skipEverythingButNewLine(text: string, index: number, options: object): number; -skipInlineComment(text: string, index: number): number; -skipTrailingComment(text: string, index: number): number; -skipNewline(text: string, index: number, options: object): number; -hasNewline(text: string, index: number, options: object): boolean; -hasNewlineInRange(text: string, start: number, start: number): boolean; -hasSpaces(text: string, index: number, options: object): number; -makeString(rawContent: string, enclosingQuote: string, unescapeUnnecessarEscapes: boolean): string; -getNextNonSpaceNonCommentCharacterIndex(text: string, node: object, options: object): number; -isNextLineEmptyAfterIndex(text: string, index: number): boolean; -isNextLineEmpty(text: string, node: object, options: object): boolean; -isPreviousLineEmpty(text: string, node: object, options: object): boolean; -mapDoc(doc: object, callback: function): void; +type Quote = '"' | "'"; +type SkipOptions = { backwards?: boolean }; +function getMaxContinuousCount(str: string, target: string): number; +function getStringWidth(text: string): number; +function getAlignmentSize(value: string, tabWidth: number, startIndex?: number): number; +function getIndentSize(value: string, tabWidth: number): number; +function skip(chars: string | RegExp): (text: string, index: number | false, opts?: SkipOptions) => number | false; +function skipWhitespace(text: string, index: number | false, opts?: SkipOptions): number | false; +function skipSpaces(text: string, index: number | false, opts?: SkipOptions): number | false; +function skipToLineEnd(text: string, index: number | false, opts?: SkipOptions): number | false; +function skipEverythingButNewLine(text: string, index: number | false, opts?: SkipOptions): number | false; +function skipInlineComment(text: string, index: number | false): number | false; +function skipTrailingComment(text: string, index: number | false): number | false; +function skipNewline(text: string, index: number | false, opts?: SkipOptions): number | false; +function hasNewline(text: string, index: number, opts?: SkipOptions): boolean; +function hasNewlineInRange(text: string, start: number, end: number): boolean; +function hasSpaces(text: string, index: number, opts?: SkipOptions): boolean; +function makeString(rawContent: string, enclosingQuote: Quote, unescapeUnnecessaryEscapes?: boolean): string; +function getNextNonSpaceNonCommentCharacterIndex(text: string, node: N, locEnd: (node: N) => number): number | false; +function isNextLineEmptyAfterIndex(text: string, index: number): boolean; +function isNextLineEmpty(text: string, node: N, locEnd: (node: N) => number): boolean; +function isPreviousLineEmpty(text: string, node: N, locStart: (node: N) => number): boolean; +function mapDoc(doc: object, callback: function): void; ``` ### Tutorials