"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.PackageName = exports.PackageNameParser = void 0;
/**
 * A configurable parser for validating and manipulating NPM package names such as `my-package` or `@scope/my-package`.
 *
 * @remarks
 * If you do not need to customize the parser configuration, it is recommended to use {@link PackageName}
 * which exposes these operations as a simple static class.
 *
 * @public
 */
class PackageNameParser {
    constructor(options = {}) {
        this._options = Object.assign({}, options);
    }
    /**
     * This attempts to parse a package name that may include a scope component.
     * The packageName must not be an empty string.
     * @remarks
     * This function will not throw an exception.
     *
     * @returns an {@link IParsedPackageNameOrError} structure whose `error` property will be
     * nonempty if the string could not be parsed.
     */
    tryParse(packageName) {
        const result = {
            scope: '',
            unscopedName: '',
            error: ''
        };
        let input = packageName;
        if (input === null || input === undefined) {
            result.error = 'The package name must not be null or undefined';
            return result;
        }
        // Rule from npmjs.com:
        // "The name must be less than or equal to 214 characters. This includes the scope for scoped packages."
        if (packageName.length > 214) {
            // Don't attempt to parse a ridiculously long input
            result.error = 'The package name cannot be longer than 214 characters';
            return result;
        }
        if (input[0] === '@') {
            const indexOfScopeSlash = input.indexOf('/');
            if (indexOfScopeSlash <= 0) {
                result.scope = input;
                result.error = `Error parsing "${packageName}": The scope must be followed by a slash`;
                return result;
            }
            // Extract the scope substring
            result.scope = input.substr(0, indexOfScopeSlash);
            input = input.substr(indexOfScopeSlash + 1);
        }
        result.unscopedName = input;
        if (result.scope === '@') {
            result.error = `Error parsing "${packageName}": The scope name cannot be empty`;
            return result;
        }
        if (result.unscopedName === '') {
            result.error = 'The package name must not be empty';
            return result;
        }
        // Rule from npmjs.com:
        // "The name can't start with a dot or an underscore."
        if (result.unscopedName[0] === '.' || result.unscopedName[0] === '_') {
            result.error = `The package name "${packageName}" starts with an invalid character`;
            return result;
        }
        // Convert "@scope/unscoped-name" --> "scopeunscoped-name"
        const nameWithoutScopeSymbols = (result.scope ? result.scope.slice(1, -1) : '') + result.unscopedName;
        if (!this._options.allowUpperCase) {
            // "New packages must not have uppercase letters in the name."
            // This can't be enforced because "old" packages are still actively maintained.
            // Example: https://www.npmjs.com/package/Base64
            // However it's pretty reasonable to require the scope to be lower case
            if (result.scope !== result.scope.toLowerCase()) {
                result.error = `The package scope "${result.scope}" must not contain upper case characters`;
                return result;
            }
        }
        // "The name ends up being part of a URL, an argument on the command line, and a folder name.
        // Therefore, the name can't contain any non-URL-safe characters"
        const match = nameWithoutScopeSymbols.match(PackageNameParser._invalidNameCharactersRegExp);
        if (match) {
            result.error = `The package name "${packageName}" contains an invalid character: "${match[0]}"`;
            return result;
        }
        return result;
    }
    /**
     * Same as {@link PackageName.tryParse}, except this throws an exception if the input
     * cannot be parsed.
     * @remarks
     * The packageName must not be an empty string.
     */
    parse(packageName) {
        const result = this.tryParse(packageName);
        if (result.error) {
            throw new Error(result.error);
        }
        return result;
    }
    /**
     * {@inheritDoc IParsedPackageName.scope}
     */
    getScope(packageName) {
        return this.parse(packageName).scope;
    }
    /**
     * {@inheritDoc IParsedPackageName.unscopedName}
     */
    getUnscopedName(packageName) {
        return this.parse(packageName).unscopedName;
    }
    /**
     * Returns true if the specified package name is valid, or false otherwise.
     * @remarks
     * This function will not throw an exception.
     */
    isValidName(packageName) {
        const result = this.tryParse(packageName);
        return !result.error;
    }
    /**
     * Throws an exception if the specified name is not a valid package name.
     * The packageName must not be an empty string.
     */
    validate(packageName) {
        this.parse(packageName);
    }
    /**
     * Combines an optional package scope with an unscoped root name.
     * @param scope - Must be either an empty string, or a scope name such as "\@example"
     * @param unscopedName - Must be a nonempty package name that does not contain a scope
     * @returns A full package name such as "\@example/some-library".
     */
    combineParts(scope, unscopedName) {
        if (scope !== '') {
            if (scope[0] !== '@') {
                throw new Error('The scope must start with an "@" character');
            }
        }
        if (scope.indexOf('/') >= 0) {
            throw new Error('The scope must not contain a "/" character');
        }
        if (unscopedName[0] === '@') {
            throw new Error('The unscopedName cannot start with an "@" character');
        }
        if (unscopedName.indexOf('/') >= 0) {
            throw new Error('The unscopedName must not contain a "/" character');
        }
        let result;
        if (scope === '') {
            result = unscopedName;
        }
        else {
            result = scope + '/' + unscopedName;
        }
        // Make sure the result is a valid package name
        this.validate(result);
        return result;
    }
}
// encodeURIComponent() escapes all characters except:  A-Z a-z 0-9 - _ . ! ~ * ' ( )
// However, these are disallowed because they are shell characters:       ! ~ * ' ( )
PackageNameParser._invalidNameCharactersRegExp = /[^A-Za-z0-9\-_\.]/;
exports.PackageNameParser = PackageNameParser;
/**
 * Provides basic operations for validating and manipulating NPM package names such as `my-package`
 * or `@scope/my-package`.
 *
 * @remarks
 * This is the default implementation of {@link PackageNameParser}, exposed as a convenient static class.
 * If you need to configure the parsing rules, use `PackageNameParser` instead.
 *
 * @public
 */
class PackageName {
    /** {@inheritDoc PackageNameParser.tryParse} */
    static tryParse(packageName) {
        return PackageName._parser.tryParse(packageName);
    }
    /** {@inheritDoc PackageNameParser.parse} */
    static parse(packageName) {
        return this._parser.parse(packageName);
    }
    /** {@inheritDoc PackageNameParser.getScope} */
    static getScope(packageName) {
        return this._parser.getScope(packageName);
    }
    /** {@inheritDoc PackageNameParser.getUnscopedName} */
    static getUnscopedName(packageName) {
        return this._parser.getUnscopedName(packageName);
    }
    /** {@inheritDoc PackageNameParser.isValidName} */
    static isValidName(packageName) {
        return this._parser.isValidName(packageName);
    }
    /** {@inheritDoc PackageNameParser.validate} */
    static validate(packageName) {
        return this._parser.validate(packageName);
    }
    /** {@inheritDoc PackageNameParser.combineParts} */
    static combineParts(scope, unscopedName) {
        return this._parser.combineParts(scope, unscopedName);
    }
}
PackageName._parser = new PackageNameParser();
exports.PackageName = PackageName;
//# sourceMappingURL=PackageName.js.map