"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.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) {
    // 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(mixinId, sourceRange);
    })
        .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.description || '').join('\n\n').trim(),
        tags: jsdocs.map((jsdoc) => jsdoc.tags)
            .reduce((acc, tags) => acc.concat(tags)),
    };
}
exports.join = join;

//# sourceMappingURL=jsdoc.js.map
