UNPKG

polymer-analyzer

Version:
216 lines 7.25 kB
"use strict"; /** * @license * Copyright (c) 2015 The Polymer Project Authors. All rights reserved. * This code may only be used under the BSD style license found at * http://polymer.github.io/LICENSE.txt * The complete set of authors may be found at * http://polymer.github.io/AUTHORS.txt * The complete set of contributors may be found at * http://polymer.github.io/CONTRIBUTORS.txt * Code distributed by Google as part of the polymer project is also * subject to an additional IP rights grant found at * http://polymer.github.io/PATENTS.txt */ Object.defineProperty(exports, "__esModule", { value: true }); const doctrine = require("doctrine"); const model_1 = require("../model/model"); /** * Given a JSDoc string (minus opening/closing comment delimiters), extract its * description and tags. */ function parseJsdoc(docs) { docs = removeLeadingAsterisks(docs); const d = doctrine.parse(docs, { unwrap: false, // lineNumbers: true, preserveWhitespace: true, }); // Strip any leading and trailing newline characters in the // description of multiline comments for readibility. // TODO(rictic): figure out if we can trim() here or not. Something something // markdown? const description = d.description && d.description.replace(/^\n+|\n+$/g, ''); return { description, tags: parseCustomTags(d.tags) }; } exports.parseJsdoc = parseJsdoc; // Tags with a name: @title name description const tagsWithNames = new Set([ 'appliesMixin', 'demo', 'hero', 'mixinFunction', 'polymerBehavior', 'pseudoElement' ]); const firstWordAndRest = /^\s*(\S*)\s*(.*)$/; function parseCustomTags(tags) { return tags.map((tag) => { if (tag.description != null && tagsWithNames.has(tag.title)) { const match = firstWordAndRest.exec(tag.description); if (match != null) { const name = match[1]; const description = match[2]; return Object.assign({}, tag, { name, description }); } } return tag; }); } /** * removes leading *, and any space before it */ function removeLeadingAsterisks(description) { return description.split('\n') .map(function (line) { // remove leading '\s*' from each line const match = line.trim().match(/^[\s]*\*\s?(.*)$/); return match ? match[1] : line; }) .join('\n'); } exports.removeLeadingAsterisks = removeLeadingAsterisks; function hasTag(jsdoc, title) { return getTag(jsdoc, title) !== undefined; } exports.hasTag = hasTag; /** * Finds the first JSDoc tag matching `title`. */ function getTag(jsdoc, title) { return jsdoc && jsdoc.tags && jsdoc.tags.find((t) => t.title === title); } exports.getTag = getTag; function unindent(text) { if (!text) { return text; } const lines = text.replace(/\t/g, ' ').split('\n'); const indent = lines.reduce(function (prev, line) { if (/^\s*$/.test(line)) { return prev; // Completely ignore blank lines. } const lineIndent = line.match(/^(\s*)/)[0].length; if (prev === null) { return lineIndent; } return lineIndent < prev ? lineIndent : prev; }, 0); return lines .map(function (l) { return l.substr(indent); }) .join('\n'); } exports.unindent = unindent; function isAnnotationEmpty(docs) { return docs === undefined || docs.tags.length === 0 && docs.description.trim() === ''; } exports.isAnnotationEmpty = isAnnotationEmpty; const privacyTags = new Set(['public', 'private', 'protected']); function getPrivacy(jsdoc) { return jsdoc && jsdoc.tags && jsdoc.tags.filter((t) => privacyTags.has(t.title)) .map((t) => t.title)[0]; } exports.getPrivacy = getPrivacy; /** * Returns the mixin applications, in the form of ScannedReferences, for the * jsdocs of class. * * The references are returned in presumed order of application - from furthest * up the prototype chain to closest to the subclass. */ function getMixinApplications(document, node, docs, warnings, path) { // TODO(justinfagnani): remove @mixes support const appliesMixinAnnotations = docs.tags.filter((tag) => tag.title === 'appliesMixin' || tag.title === 'mixes'); return appliesMixinAnnotations .map((annotation) => { const mixinId = annotation.name; // TODO(justinfagnani): we need source ranges for jsdoc // annotations const sourceRange = document.sourceRangeForNode(node); if (mixinId === undefined) { warnings.push(new model_1.Warning({ code: 'class-mixes-annotation-no-id', message: '@appliesMixin annotation with no identifier. Usage `@appliesMixin MixinName`', severity: model_1.Severity.WARNING, sourceRange, parsedDocument: document })); return; } return new model_1.ScannedReference('element-mixin', mixinId, sourceRange, undefined, path); }) .filter((m) => m !== undefined); } exports.getMixinApplications = getMixinApplications; function extractDemos(jsdoc) { if (!jsdoc || !jsdoc.tags) { return []; } const demos = []; const demoUrls = new Set(); for (const tag of jsdoc.tags.filter((tag) => tag.title === 'demo' && tag.name)) { const demoUrl = tag.name; if (demoUrls.has(demoUrl)) { continue; } demoUrls.add(demoUrl); demos.push({ desc: tag.description || undefined, path: demoUrl, }); } return demos; } exports.extractDemos = extractDemos; function join(...jsdocs) { return { description: jsdocs.map((jsdoc) => jsdoc && jsdoc.description || '') .join('\n\n') .trim(), tags: jsdocs.map((jsdoc) => jsdoc && jsdoc.tags || []) .reduce((acc, tags) => acc.concat(tags)), }; } exports.join = join; /** * Assume that if the same symbol is documented in multiple places, the longer * description is probably the intended one. * * TODO(rictic): unify logic with join(...)'s above. */ function pickBestDescription(...descriptions) { let description = ''; for (const desc of descriptions) { if (desc && desc.length > description.length) { description = desc; } } return description; } exports.pickBestDescription = pickBestDescription; /** * Extracts the description from a jsdoc annotation and uses * known descriptive tags if no explicit description is set. */ function getDescription(jsdocAnn) { if (jsdocAnn.description) { return jsdocAnn.description; } // These tags can be used to describe a field. // e.g.: // /** @type {string} the name of the animal */ // this.name = name || 'Rex'; const tagSet = new Set(['public', 'private', 'protected', 'type']); for (const tag of jsdocAnn.tags) { if (tagSet.has(tag.title) && tag.description) { return tag.description; } } } exports.getDescription = getDescription; //# sourceMappingURL=jsdoc.js.map