| var CommentParser = (function (exports) { |
| 'use strict'; |
| |
| /** @deprecated */ |
| exports.Markers = void 0; |
| (function (Markers) { |
| Markers["start"] = "/**"; |
| Markers["nostart"] = "/***"; |
| Markers["delim"] = "*"; |
| Markers["end"] = "*/"; |
| })(exports.Markers || (exports.Markers = {})); |
| |
| function isSpace(source) { |
| return /^\s+$/.test(source); |
| } |
| function splitCR(source) { |
| const matches = source.match(/\r+$/); |
| return matches == null |
| ? ['', source] |
| : [source.slice(-matches[0].length), source.slice(0, -matches[0].length)]; |
| } |
| function splitSpace(source) { |
| const matches = source.match(/^\s+/); |
| return matches == null |
| ? ['', source] |
| : [source.slice(0, matches[0].length), source.slice(matches[0].length)]; |
| } |
| function splitLines(source) { |
| return source.split(/\n/); |
| } |
| function seedBlock(block = {}) { |
| return Object.assign({ description: '', tags: [], source: [], problems: [] }, block); |
| } |
| function seedSpec(spec = {}) { |
| return Object.assign({ tag: '', name: '', type: '', optional: false, description: '', problems: [], source: [] }, spec); |
| } |
| function seedTokens(tokens = {}) { |
| return Object.assign({ start: '', delimiter: '', postDelimiter: '', tag: '', postTag: '', name: '', postName: '', type: '', postType: '', description: '', end: '', lineEnd: '' }, tokens); |
| } |
| /** |
| * Assures Block.tags[].source contains references to the Block.source items, |
| * using Block.source as a source of truth. This is a counterpart of rewireSpecs |
| * @param block parsed coments block |
| */ |
| function rewireSource(block) { |
| const source = block.source.reduce((acc, line) => acc.set(line.number, line), new Map()); |
| for (const spec of block.tags) { |
| spec.source = spec.source.map((line) => source.get(line.number)); |
| } |
| return block; |
| } |
| /** |
| * Assures Block.source contains references to the Block.tags[].source items, |
| * using Block.tags[].source as a source of truth. This is a counterpart of rewireSource |
| * @param block parsed coments block |
| */ |
| function rewireSpecs(block) { |
| const source = block.tags.reduce((acc, spec) => spec.source.reduce((acc, line) => acc.set(line.number, line), acc), new Map()); |
| block.source = block.source.map((line) => source.get(line.number) || line); |
| return block; |
| } |
| |
| const reTag = /^@\S+/; |
| /** |
| * Creates configured `Parser` |
| * @param {Partial<Options>} options |
| */ |
| function getParser$3({ fence = '```', } = {}) { |
| const fencer = getFencer(fence); |
| const toggleFence = (source, isFenced) => fencer(source) ? !isFenced : isFenced; |
| return function parseBlock(source) { |
| // start with description section |
| const sections = [[]]; |
| let isFenced = false; |
| for (const line of source) { |
| if (reTag.test(line.tokens.description) && !isFenced) { |
| sections.push([line]); |
| } |
| else { |
| sections[sections.length - 1].push(line); |
| } |
| isFenced = toggleFence(line.tokens.description, isFenced); |
| } |
| return sections; |
| }; |
| } |
| function getFencer(fence) { |
| if (typeof fence === 'string') |
| return (source) => source.split(fence).length % 2 === 0; |
| return fence; |
| } |
| |
| function getParser$2({ startLine = 0, markers = exports.Markers, } = {}) { |
| let block = null; |
| let num = startLine; |
| return function parseSource(source) { |
| let rest = source; |
| const tokens = seedTokens(); |
| [tokens.lineEnd, rest] = splitCR(rest); |
| [tokens.start, rest] = splitSpace(rest); |
| if (block === null && |
| rest.startsWith(markers.start) && |
| !rest.startsWith(markers.nostart)) { |
| block = []; |
| tokens.delimiter = rest.slice(0, markers.start.length); |
| rest = rest.slice(markers.start.length); |
| [tokens.postDelimiter, rest] = splitSpace(rest); |
| } |
| if (block === null) { |
| num++; |
| return null; |
| } |
| const isClosed = rest.trimRight().endsWith(markers.end); |
| if (tokens.delimiter === '' && |
| rest.startsWith(markers.delim) && |
| !rest.startsWith(markers.end)) { |
| tokens.delimiter = markers.delim; |
| rest = rest.slice(markers.delim.length); |
| [tokens.postDelimiter, rest] = splitSpace(rest); |
| } |
| if (isClosed) { |
| const trimmed = rest.trimRight(); |
| tokens.end = rest.slice(trimmed.length - markers.end.length); |
| rest = trimmed.slice(0, -markers.end.length); |
| } |
| tokens.description = rest; |
| block.push({ number: num, source, tokens }); |
| num++; |
| if (isClosed) { |
| const result = block.slice(); |
| block = null; |
| return result; |
| } |
| return null; |
| }; |
| } |
| |
| function getParser$1({ tokenizers }) { |
| return function parseSpec(source) { |
| var _a; |
| let spec = seedSpec({ source }); |
| for (const tokenize of tokenizers) { |
| spec = tokenize(spec); |
| if ((_a = spec.problems[spec.problems.length - 1]) === null || _a === void 0 ? void 0 : _a.critical) |
| break; |
| } |
| return spec; |
| }; |
| } |
| |
| /** |
| * Splits the `@prefix` from remaining `Spec.lines[].token.description` into the `tag` token, |
| * and populates `spec.tag` |
| */ |
| function tagTokenizer() { |
| return (spec) => { |
| const { tokens } = spec.source[0]; |
| const match = tokens.description.match(/\s*(@(\S+))(\s*)/); |
| if (match === null) { |
| spec.problems.push({ |
| code: 'spec:tag:prefix', |
| message: 'tag should start with "@" symbol', |
| line: spec.source[0].number, |
| critical: true, |
| }); |
| return spec; |
| } |
| tokens.tag = match[1]; |
| tokens.postTag = match[3]; |
| tokens.description = tokens.description.slice(match[0].length); |
| spec.tag = match[2]; |
| return spec; |
| }; |
| } |
| |
| /** |
| * Sets splits remaining `Spec.lines[].tokes.description` into `type` and `description` |
| * tokens and populates Spec.type` |
| * |
| * @param {Spacing} spacing tells how to deal with a whitespace |
| * for type values going over multiple lines |
| */ |
| function typeTokenizer(spacing = 'compact') { |
| const join = getJoiner$1(spacing); |
| return (spec) => { |
| let curlies = 0; |
| let lines = []; |
| for (const [i, { tokens }] of spec.source.entries()) { |
| let type = ''; |
| if (i === 0 && tokens.description[0] !== '{') |
| return spec; |
| for (const ch of tokens.description) { |
| if (ch === '{') |
| curlies++; |
| if (ch === '}') |
| curlies--; |
| type += ch; |
| if (curlies === 0) |
| break; |
| } |
| lines.push([tokens, type]); |
| if (curlies === 0) |
| break; |
| } |
| if (curlies !== 0) { |
| spec.problems.push({ |
| code: 'spec:type:unpaired-curlies', |
| message: 'unpaired curlies', |
| line: spec.source[0].number, |
| critical: true, |
| }); |
| return spec; |
| } |
| const parts = []; |
| const offset = lines[0][0].postDelimiter.length; |
| for (const [i, [tokens, type]] of lines.entries()) { |
| tokens.type = type; |
| if (i > 0) { |
| tokens.type = tokens.postDelimiter.slice(offset) + type; |
| tokens.postDelimiter = tokens.postDelimiter.slice(0, offset); |
| } |
| [tokens.postType, tokens.description] = splitSpace(tokens.description.slice(type.length)); |
| parts.push(tokens.type); |
| } |
| parts[0] = parts[0].slice(1); |
| parts[parts.length - 1] = parts[parts.length - 1].slice(0, -1); |
| spec.type = join(parts); |
| return spec; |
| }; |
| } |
| const trim = (x) => x.trim(); |
| function getJoiner$1(spacing) { |
| if (spacing === 'compact') |
| return (t) => t.map(trim).join(''); |
| else if (spacing === 'preserve') |
| return (t) => t.join('\n'); |
| else |
| return spacing; |
| } |
| |
| const isQuoted = (s) => s && s.startsWith('"') && s.endsWith('"'); |
| /** |
| * Splits remaining `spec.lines[].tokens.description` into `name` and `descriptions` tokens, |
| * and populates the `spec.name` |
| */ |
| function nameTokenizer() { |
| const typeEnd = (num, { tokens }, i) => tokens.type === '' ? num : i; |
| return (spec) => { |
| // look for the name in the line where {type} ends |
| const { tokens } = spec.source[spec.source.reduce(typeEnd, 0)]; |
| const source = tokens.description.trimLeft(); |
| const quotedGroups = source.split('"'); |
| // if it starts with quoted group, assume it is a literal |
| if (quotedGroups.length > 1 && |
| quotedGroups[0] === '' && |
| quotedGroups.length % 2 === 1) { |
| spec.name = quotedGroups[1]; |
| tokens.name = `"${quotedGroups[1]}"`; |
| [tokens.postName, tokens.description] = splitSpace(source.slice(tokens.name.length)); |
| return spec; |
| } |
| let brackets = 0; |
| let name = ''; |
| let optional = false; |
| let defaultValue; |
| // assume name is non-space string or anything wrapped into brackets |
| for (const ch of source) { |
| if (brackets === 0 && isSpace(ch)) |
| break; |
| if (ch === '[') |
| brackets++; |
| if (ch === ']') |
| brackets--; |
| name += ch; |
| } |
| if (brackets !== 0) { |
| spec.problems.push({ |
| code: 'spec:name:unpaired-brackets', |
| message: 'unpaired brackets', |
| line: spec.source[0].number, |
| critical: true, |
| }); |
| return spec; |
| } |
| const nameToken = name; |
| if (name[0] === '[' && name[name.length - 1] === ']') { |
| optional = true; |
| name = name.slice(1, -1); |
| const parts = name.split('='); |
| name = parts[0].trim(); |
| if (parts[1] !== undefined) |
| defaultValue = parts.slice(1).join('=').trim(); |
| if (name === '') { |
| spec.problems.push({ |
| code: 'spec:name:empty-name', |
| message: 'empty name', |
| line: spec.source[0].number, |
| critical: true, |
| }); |
| return spec; |
| } |
| if (defaultValue === '') { |
| spec.problems.push({ |
| code: 'spec:name:empty-default', |
| message: 'empty default value', |
| line: spec.source[0].number, |
| critical: true, |
| }); |
| return spec; |
| } |
| // has "=" and is not a string, except for "=>" |
| if (!isQuoted(defaultValue) && /=(?!>)/.test(defaultValue)) { |
| spec.problems.push({ |
| code: 'spec:name:invalid-default', |
| message: 'invalid default value syntax', |
| line: spec.source[0].number, |
| critical: true, |
| }); |
| return spec; |
| } |
| } |
| spec.optional = optional; |
| spec.name = name; |
| tokens.name = nameToken; |
| if (defaultValue !== undefined) |
| spec.default = defaultValue; |
| [tokens.postName, tokens.description] = splitSpace(source.slice(tokens.name.length)); |
| return spec; |
| }; |
| } |
| |
| /** |
| * Makes no changes to `spec.lines[].tokens` but joins them into `spec.description` |
| * following given spacing srtategy |
| * @param {Spacing} spacing tells how to handle the whitespace |
| * @param {BlockMarkers} markers tells how to handle comment block delimitation |
| */ |
| function descriptionTokenizer(spacing = 'compact', markers = exports.Markers) { |
| const join = getJoiner(spacing); |
| return (spec) => { |
| spec.description = join(spec.source, markers); |
| return spec; |
| }; |
| } |
| function getJoiner(spacing) { |
| if (spacing === 'compact') |
| return compactJoiner; |
| if (spacing === 'preserve') |
| return preserveJoiner; |
| return spacing; |
| } |
| function compactJoiner(lines, markers = exports.Markers) { |
| return lines |
| .map(({ tokens: { description } }) => description.trim()) |
| .filter((description) => description !== '') |
| .join(' '); |
| } |
| const lineNo = (num, { tokens }, i) => tokens.type === '' ? num : i; |
| const getDescription = ({ tokens }) => (tokens.delimiter === '' ? tokens.start : tokens.postDelimiter.slice(1)) + |
| tokens.description; |
| function preserveJoiner(lines, markers = exports.Markers) { |
| if (lines.length === 0) |
| return ''; |
| // skip the opening line with no description |
| if (lines[0].tokens.description === '' && |
| lines[0].tokens.delimiter === markers.start) |
| lines = lines.slice(1); |
| // skip the closing line with no description |
| const lastLine = lines[lines.length - 1]; |
| if (lastLine !== undefined && |
| lastLine.tokens.description === '' && |
| lastLine.tokens.end.endsWith(markers.end)) |
| lines = lines.slice(0, -1); |
| // description starts at the last line of type definition |
| lines = lines.slice(lines.reduce(lineNo, 0)); |
| return lines.map(getDescription).join('\n'); |
| } |
| |
| function getParser({ startLine = 0, fence = '```', spacing = 'compact', markers = exports.Markers, tokenizers = [ |
| tagTokenizer(), |
| typeTokenizer(spacing), |
| nameTokenizer(), |
| descriptionTokenizer(spacing), |
| ], } = {}) { |
| if (startLine < 0 || startLine % 1 > 0) |
| throw new Error('Invalid startLine'); |
| const parseSource = getParser$2({ startLine, markers }); |
| const parseBlock = getParser$3({ fence }); |
| const parseSpec = getParser$1({ tokenizers }); |
| const joinDescription = getJoiner(spacing); |
| return function (source) { |
| const blocks = []; |
| for (const line of splitLines(source)) { |
| const lines = parseSource(line); |
| if (lines === null) |
| continue; |
| const sections = parseBlock(lines); |
| const specs = sections.slice(1).map(parseSpec); |
| blocks.push({ |
| description: joinDescription(sections[0], markers), |
| tags: specs, |
| source: lines, |
| problems: specs.reduce((acc, spec) => acc.concat(spec.problems), []), |
| }); |
| } |
| return blocks; |
| }; |
| } |
| |
| function join(tokens) { |
| return (tokens.start + |
| tokens.delimiter + |
| tokens.postDelimiter + |
| tokens.tag + |
| tokens.postTag + |
| tokens.type + |
| tokens.postType + |
| tokens.name + |
| tokens.postName + |
| tokens.description + |
| tokens.end + |
| tokens.lineEnd); |
| } |
| function getStringifier() { |
| return (block) => block.source.map(({ tokens }) => join(tokens)).join('\n'); |
| } |
| |
| var __rest$2 = (window && window.__rest) || function (s, e) { |
| var t = {}; |
| for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) |
| t[p] = s[p]; |
| if (s != null && typeof Object.getOwnPropertySymbols === "function") |
| for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { |
| if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) |
| t[p[i]] = s[p[i]]; |
| } |
| return t; |
| }; |
| const zeroWidth$1 = { |
| start: 0, |
| tag: 0, |
| type: 0, |
| name: 0, |
| }; |
| const getWidth = (markers = exports.Markers) => (w, { tokens: t }) => ({ |
| start: t.delimiter === markers.start ? t.start.length : w.start, |
| tag: Math.max(w.tag, t.tag.length), |
| type: Math.max(w.type, t.type.length), |
| name: Math.max(w.name, t.name.length), |
| }); |
| const space = (len) => ''.padStart(len, ' '); |
| function align$1(markers = exports.Markers) { |
| let intoTags = false; |
| let w; |
| function update(line) { |
| const tokens = Object.assign({}, line.tokens); |
| if (tokens.tag !== '') |
| intoTags = true; |
| const isEmpty = tokens.tag === '' && |
| tokens.name === '' && |
| tokens.type === '' && |
| tokens.description === ''; |
| // dangling '*/' |
| if (tokens.end === markers.end && isEmpty) { |
| tokens.start = space(w.start + 1); |
| return Object.assign(Object.assign({}, line), { tokens }); |
| } |
| switch (tokens.delimiter) { |
| case markers.start: |
| tokens.start = space(w.start); |
| break; |
| case markers.delim: |
| tokens.start = space(w.start + 1); |
| break; |
| default: |
| tokens.delimiter = ''; |
| tokens.start = space(w.start + 2); // compensate delimiter |
| } |
| if (!intoTags) { |
| tokens.postDelimiter = tokens.description === '' ? '' : ' '; |
| return Object.assign(Object.assign({}, line), { tokens }); |
| } |
| const nothingAfter = { |
| delim: false, |
| tag: false, |
| type: false, |
| name: false, |
| }; |
| if (tokens.description === '') { |
| nothingAfter.name = true; |
| tokens.postName = ''; |
| if (tokens.name === '') { |
| nothingAfter.type = true; |
| tokens.postType = ''; |
| if (tokens.type === '') { |
| nothingAfter.tag = true; |
| tokens.postTag = ''; |
| if (tokens.tag === '') { |
| nothingAfter.delim = true; |
| } |
| } |
| } |
| } |
| tokens.postDelimiter = nothingAfter.delim ? '' : ' '; |
| if (!nothingAfter.tag) |
| tokens.postTag = space(w.tag - tokens.tag.length + 1); |
| if (!nothingAfter.type) |
| tokens.postType = space(w.type - tokens.type.length + 1); |
| if (!nothingAfter.name) |
| tokens.postName = space(w.name - tokens.name.length + 1); |
| return Object.assign(Object.assign({}, line), { tokens }); |
| } |
| return (_a) => { |
| var { source } = _a, fields = __rest$2(_a, ["source"]); |
| w = source.reduce(getWidth(markers), Object.assign({}, zeroWidth$1)); |
| return rewireSource(Object.assign(Object.assign({}, fields), { source: source.map(update) })); |
| }; |
| } |
| |
| var __rest$1 = (window && window.__rest) || function (s, e) { |
| var t = {}; |
| for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) |
| t[p] = s[p]; |
| if (s != null && typeof Object.getOwnPropertySymbols === "function") |
| for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { |
| if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) |
| t[p[i]] = s[p[i]]; |
| } |
| return t; |
| }; |
| const pull = (offset) => (str) => str.slice(offset); |
| const push = (offset) => { |
| const space = ''.padStart(offset, ' '); |
| return (str) => str + space; |
| }; |
| function indent(pos) { |
| let shift; |
| const pad = (start) => { |
| if (shift === undefined) { |
| const offset = pos - start.length; |
| shift = offset > 0 ? push(offset) : pull(-offset); |
| } |
| return shift(start); |
| }; |
| const update = (line) => (Object.assign(Object.assign({}, line), { tokens: Object.assign(Object.assign({}, line.tokens), { start: pad(line.tokens.start) }) })); |
| return (_a) => { |
| var { source } = _a, fields = __rest$1(_a, ["source"]); |
| return rewireSource(Object.assign(Object.assign({}, fields), { source: source.map(update) })); |
| }; |
| } |
| |
| var __rest = (window && window.__rest) || function (s, e) { |
| var t = {}; |
| for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) |
| t[p] = s[p]; |
| if (s != null && typeof Object.getOwnPropertySymbols === "function") |
| for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { |
| if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) |
| t[p[i]] = s[p[i]]; |
| } |
| return t; |
| }; |
| function crlf(ending) { |
| function update(line) { |
| return Object.assign(Object.assign({}, line), { tokens: Object.assign(Object.assign({}, line.tokens), { lineEnd: ending === 'LF' ? '' : '\r' }) }); |
| } |
| return (_a) => { |
| var { source } = _a, fields = __rest(_a, ["source"]); |
| return rewireSource(Object.assign(Object.assign({}, fields), { source: source.map(update) })); |
| }; |
| } |
| |
| function flow(...transforms) { |
| return (block) => transforms.reduce((block, t) => t(block), block); |
| } |
| |
| const zeroWidth = { |
| line: 0, |
| start: 0, |
| delimiter: 0, |
| postDelimiter: 0, |
| tag: 0, |
| postTag: 0, |
| name: 0, |
| postName: 0, |
| type: 0, |
| postType: 0, |
| description: 0, |
| end: 0, |
| lineEnd: 0, |
| }; |
| const headers = { lineEnd: 'CR' }; |
| const fields = Object.keys(zeroWidth); |
| const repr = (x) => (isSpace(x) ? `{${x.length}}` : x); |
| const frame = (line) => '|' + line.join('|') + '|'; |
| const align = (width, tokens) => Object.keys(tokens).map((k) => repr(tokens[k]).padEnd(width[k])); |
| function inspect({ source }) { |
| var _a, _b; |
| if (source.length === 0) |
| return ''; |
| const width = Object.assign({}, zeroWidth); |
| for (const f of fields) |
| width[f] = ((_a = headers[f]) !== null && _a !== void 0 ? _a : f).length; |
| for (const { number, tokens } of source) { |
| width.line = Math.max(width.line, number.toString().length); |
| for (const k in tokens) |
| width[k] = Math.max(width[k], repr(tokens[k]).length); |
| } |
| const lines = [[], []]; |
| for (const f of fields) |
| lines[0].push(((_b = headers[f]) !== null && _b !== void 0 ? _b : f).padEnd(width[f])); |
| for (const f of fields) |
| lines[1].push('-'.padEnd(width[f], '-')); |
| for (const { number, tokens } of source) { |
| const line = number.toString().padStart(width.line); |
| lines.push([line, ...align(width, tokens)]); |
| } |
| return lines.map(frame).join('\n'); |
| } |
| |
| function parse(source, options = {}) { |
| return getParser(options)(source); |
| } |
| const stringify = getStringifier(); |
| const transforms = { |
| flow: flow, |
| align: align$1, |
| indent: indent, |
| crlf: crlf, |
| }; |
| const tokenizers = { |
| tag: tagTokenizer, |
| type: typeTokenizer, |
| name: nameTokenizer, |
| description: descriptionTokenizer, |
| }; |
| const util = { rewireSpecs, rewireSource, seedBlock, seedTokens }; |
| |
| exports.inspect = inspect; |
| exports.parse = parse; |
| exports.stringify = stringify; |
| exports.tokenizers = tokenizers; |
| exports.transforms = transforms; |
| exports.util = util; |
| |
| Object.defineProperty(exports, '__esModule', { value: true }); |
| |
| return exports; |
| |
| }({})); |