123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460 |
- "use strict";
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
- if (k2 === undefined) k2 = k;
- var desc = Object.getOwnPropertyDescriptor(m, k);
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
- desc = { enumerable: true, get: function() { return m[k]; } };
- }
- Object.defineProperty(o, k2, desc);
- }) : (function(o, m, k, k2) {
- if (k2 === undefined) k2 = k;
- o[k2] = m[k];
- }));
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
- Object.defineProperty(o, "default", { enumerable: true, value: v });
- }) : function(o, v) {
- o["default"] = v;
- });
- var __importStar = (this && this.__importStar) || function (mod) {
- if (mod && mod.__esModule) return mod;
- var result = {};
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
- __setModuleDefault(result, mod);
- return result;
- };
- var __importDefault = (this && this.__importDefault) || function (mod) {
- return (mod && mod.__esModule) ? mod : { "default": mod };
- };
- Object.defineProperty(exports, "__esModule", { value: true });
- exports.parseSync = exports.parse = exports.parseFromFilesSync = exports.parseFromFiles = exports.parseString = exports.parseBuffer = void 0;
- const fs = __importStar(require("fs"));
- const path = __importStar(require("path"));
- const semver = __importStar(require("semver"));
- const minimatch_1 = require("minimatch");
- const wasm_1 = require("@one-ini/wasm");
- // @ts-ignore So we can set the rootDir to be 'lib', without processing
- // package.json
- const package_json_1 = __importDefault(require("../package.json"));
- const escapedSep = new RegExp(path.sep.replace(/\\/g, '\\\\'), 'g');
- const matchOptions = { matchBase: true, dot: true, noext: true };
- // These are specified by the editorconfig script
- /* eslint-disable @typescript-eslint/naming-convention */
- const knownProps = {
- end_of_line: true,
- indent_style: true,
- indent_size: true,
- insert_final_newline: true,
- trim_trailing_whitespace: true,
- charset: true,
- };
- /**
- * Parse a buffer using the faster one-ini WASM approach into something
- * relatively easy to deal with in JS.
- *
- * @param data UTF8-encoded bytes.
- * @returns Parsed contents. Will be truncated if there was a parse error.
- */
- function parseBuffer(data) {
- const parsed = (0, wasm_1.parse_to_uint32array)(data);
- let cur = {};
- const res = [[null, cur]];
- let key = null;
- for (let i = 0; i < parsed.length; i += 3) {
- switch (parsed[i]) {
- case wasm_1.TokenTypes.Section: {
- cur = {};
- res.push([
- data.toString('utf8', parsed[i + 1], parsed[i + 2]),
- cur,
- ]);
- break;
- }
- case wasm_1.TokenTypes.Key:
- key = data.toString('utf8', parsed[i + 1], parsed[i + 2]);
- break;
- case wasm_1.TokenTypes.Value: {
- cur[key] = data.toString('utf8', parsed[i + 1], parsed[i + 2]);
- break;
- }
- default: // Comments, etc.
- break;
- }
- }
- return res;
- }
- exports.parseBuffer = parseBuffer;
- /**
- * Parses a string. If possible, you should always use ParseBuffer instead,
- * since this function does a UTF16-to-UTF8 conversion first.
- *
- * @param data String to parse.
- * @returns Parsed contents. Will be truncated if there was a parse error.
- * @deprecated Use {@link ParseBuffer} instead.
- */
- function parseString(data) {
- return parseBuffer(Buffer.from(data));
- }
- exports.parseString = parseString;
- /**
- * Gets a list of *potential* filenames based on the path of the target
- * filename.
- *
- * @param filepath File we are asking about.
- * @param options Config file name and root directory
- * @returns List of potential fully-qualified filenames that might have configs.
- */
- function getConfigFileNames(filepath, options) {
- const paths = [];
- do {
- filepath = path.dirname(filepath);
- paths.push(path.join(filepath, options.config));
- } while (filepath !== options.root);
- return paths;
- }
- /**
- * Take a combined config for the target file, and tweak it slightly based on
- * which editorconfig version's rules we are using.
- *
- * @param matches Combined config.
- * @param version Editorconfig version to enforce.
- * @returns The passed-in matches object, modified in place.
- */
- function processMatches(matches, version) {
- // Set indent_size to 'tab' if indent_size is unspecified and
- // indent_style is set to 'tab'.
- if ('indent_style' in matches
- && matches.indent_style === 'tab'
- && !('indent_size' in matches)
- && semver.gte(version, '0.10.0')) {
- matches.indent_size = 'tab';
- }
- // Set tab_width to indent_size if indent_size is specified and
- // tab_width is unspecified
- if ('indent_size' in matches
- && !('tab_width' in matches)
- && matches.indent_size !== 'tab') {
- matches.tab_width = matches.indent_size;
- }
- // Set indent_size to tab_width if indent_size is 'tab'
- if ('indent_size' in matches
- && 'tab_width' in matches
- && matches.indent_size === 'tab') {
- matches.indent_size = matches.tab_width;
- }
- return matches;
- }
- function buildFullGlob(pathPrefix, glob) {
- switch (glob.indexOf('/')) {
- case -1:
- glob = '**/' + glob;
- break;
- case 0:
- glob = glob.substring(1);
- break;
- default:
- break;
- }
- // braces_escaped_backslash2
- // backslash_not_on_windows
- glob = glob.replace(/\\\\/g, '\\\\\\\\');
- // star_star_over_separator{1,3,5,6,9,15}
- glob = glob.replace(/\*\*/g, '{*,**/**/**}');
- // NOT path.join. Must stay in forward slashes.
- return new minimatch_1.Minimatch(`${pathPrefix}/${glob}`, matchOptions);
- }
- /**
- * Normalize the properties read from a config file so that their key names
- * are lowercased for the known properties, and their values are parsed into
- * the correct JS types if possible.
- *
- * @param options
- * @returns
- */
- function normalizeProps(options) {
- const props = {};
- for (const key in options) {
- if (options.hasOwnProperty(key)) {
- const value = options[key];
- const key2 = key.toLowerCase();
- let value2 = value;
- // @ts-ignore -- Fix types here
- if (knownProps[key2]) {
- // All of the values for the known props are lowercase.
- value2 = String(value).toLowerCase();
- }
- try {
- value2 = JSON.parse(String(value));
- }
- catch (e) { }
- if (typeof value2 === 'undefined' || value2 === null) {
- // null and undefined are values specific to JSON (no special meaning
- // in editorconfig) & should just be returned as regular strings.
- value2 = String(value);
- }
- // @ts-ignore -- Fix types here
- props[key2] = value2;
- }
- }
- return props;
- }
- /**
- * Take the contents of a config file, and prepare it for use. If a cache is
- * provided, the result will be stored there. As such, all of the higher-CPU
- * work that is per-file should be done here.
- *
- * @param filepath The fully-qualified path of the file.
- * @param contents The contents as read from that file.
- * @param options Access to the cache.
- * @returns Processed file with globs pre-computed.
- */
- function processFileContents(filepath, contents, options) {
- let res;
- if (!contents) {
- // Negative cache
- res = {
- root: false,
- notfound: true,
- name: filepath,
- config: [[null, {}, null]],
- };
- }
- else {
- let pathPrefix = path.dirname(filepath);
- if (path.sep !== '/') {
- // Windows-only
- pathPrefix = pathPrefix.replace(escapedSep, '/');
- }
- // After Windows path backslash's are turned into slashes, so that
- // the backslashes we add here aren't turned into forward slashes:
- // All of these characters are special to minimatch, but can be
- // forced into path names on many file systems. Escape them. Note
- // that these are in the order of the case statement in minimatch.
- pathPrefix = pathPrefix.replace(/[?*+@!()|[\]{}]/g, '\\$&');
- // I can't think of a way for this to happen in the filesystems I've
- // seen (because of the path.dirname above), but let's be thorough.
- pathPrefix = pathPrefix.replace(/^#/, '\\#');
- const globbed = parseBuffer(contents).map(([name, body]) => [
- name,
- normalizeProps(body),
- name ? buildFullGlob(pathPrefix, name) : null,
- ]);
- res = {
- root: !!globbed[0][1].root,
- name: filepath,
- config: globbed,
- };
- }
- if (options.cache) {
- options.cache.set(filepath, res);
- }
- return res;
- }
- /**
- * Get a file from the cache, or read its contents from disk, process, and
- * insert into the cache (if configured).
- *
- * @param filepath The fully-qualified path of the config file.
- * @param options Access to the cache, if configured.
- * @returns The processed file, or undefined if there was an error reading it.
- */
- async function getConfig(filepath, options) {
- if (options.cache) {
- const cached = options.cache.get(filepath);
- if (cached) {
- return cached;
- }
- }
- const contents = await new Promise(resolve => {
- fs.readFile(filepath, (_, buf) => {
- // Ignore errors. contents will be undefined
- // Perhaps only file-not-found should be ignored?
- resolve(buf);
- });
- });
- return processFileContents(filepath, contents, options);
- }
- /**
- * Get a file from the cache, or read its contents from disk, process, and
- * insert into the cache (if configured). Synchronous.
- *
- * @param filepath The fully-qualified path of the config file.
- * @param options Access to the cache, if configured.
- * @returns The processed file, or undefined if there was an error reading it.
- */
- function getConfigSync(filepath, options) {
- if (options.cache) {
- const cached = options.cache.get(filepath);
- if (cached) {
- return cached;
- }
- }
- let contents;
- try {
- contents = fs.readFileSync(filepath);
- }
- catch (_) {
- // Ignore errors
- // Perhaps only file-not-found should be ignored
- }
- return processFileContents(filepath, contents, options);
- }
- /**
- * Get all of the possibly-existing config files, stopping when one is marked
- * root=true.
- *
- * @param files List of potential files
- * @param options Access to cache if configured
- * @returns List of processed configs for existing files
- */
- async function getAllConfigs(files, options) {
- const configs = [];
- for (const file of files) {
- const config = await getConfig(file, options);
- if (!config.notfound) {
- configs.push(config);
- if (config.root) {
- break;
- }
- }
- }
- return configs;
- }
- /**
- * Get all of the possibly-existing config files, stopping when one is marked
- * root=true. Synchronous.
- *
- * @param files List of potential files
- * @param options Access to cache if configured
- * @returns List of processed configs for existing files
- */
- function getAllConfigsSync(files, options) {
- const configs = [];
- for (const file of files) {
- const config = getConfigSync(file, options);
- if (!config.notfound) {
- configs.push(config);
- if (config.root) {
- break;
- }
- }
- }
- return configs;
- }
- /**
- * Normalize the options passed in to the publicly-visible functions.
- *
- * @param filepath The name of the target file, relative to process.cwd().
- * @param options Potentially-incomplete options.
- * @returns The fully-qualified target file name and the normalized options.
- */
- function opts(filepath, options = {}) {
- const resolvedFilePath = path.resolve(filepath);
- return [
- resolvedFilePath,
- {
- config: options.config || '.editorconfig',
- version: options.version || package_json_1.default.version,
- root: path.resolve(options.root || path.parse(resolvedFilePath).root),
- files: options.files,
- cache: options.cache,
- },
- ];
- }
- /**
- * Low-level interface, which exists only for backward-compatibility.
- * Deprecated.
- *
- * @param filepath The name of the target file, relative to process.cwd().
- * @param files A promise for a list of objects describing the files.
- * @param options All options
- * @returns The properties found for filepath
- * @deprecated
- */
- async function parseFromFiles(filepath, files, options = {}) {
- return parseFromFilesSync(filepath, await files, options);
- }
- exports.parseFromFiles = parseFromFiles;
- /**
- * Low-level interface, which exists only for backward-compatibility.
- * Deprecated.
- *
- * @param filepath The name of the target file, relative to process.cwd().
- * @param files A list of objects describing the files.
- * @param options All options
- * @returns The properties found for filepath
- * @deprecated
- */
- function parseFromFilesSync(filepath, files, options = {}) {
- const [resolvedFilePath, processedOptions] = opts(filepath, options);
- const configs = [];
- for (const ecf of files) {
- let cfg;
- if (!options.cache || !(cfg = options.cache.get(ecf.name))) { // Single "="!
- cfg = processFileContents(ecf.name, ecf.contents, processedOptions);
- }
- if (!cfg.notfound) {
- configs.push(cfg);
- }
- if (cfg.root) {
- break;
- }
- }
- return combine(resolvedFilePath, configs, processedOptions);
- }
- exports.parseFromFilesSync = parseFromFilesSync;
- /**
- * Combine the pre-parsed results of all matching config file sections, in
- * order.
- *
- * @param filepath The target file path
- * @param configs All of the found config files, up to the root
- * @param options Adds to `options.files` if it exists
- * @returns Combined properties
- */
- function combine(filepath, configs, options) {
- const ret = configs.reverse().reduce((props, processed) => {
- for (const [name, body, glob] of processed.config) {
- if (glob && glob.match(filepath)) {
- Object.assign(props, body);
- if (options.files) {
- options.files.push({
- fileName: processed.name,
- glob: name,
- });
- }
- }
- }
- return props;
- }, {});
- return processMatches(ret, options.version);
- }
- /**
- * Find all of the properties from matching sections in config files in the
- * same directory or toward the root of the filesystem.
- *
- * @param filepath The target file name, relative to process.cwd().
- * @param options All options
- * @returns Combined properties for the target file
- */
- async function parse(filepath, options = {}) {
- const [resolvedFilePath, processedOptions] = opts(filepath, options);
- const filepaths = getConfigFileNames(resolvedFilePath, processedOptions);
- const configs = await getAllConfigs(filepaths, processedOptions);
- return combine(resolvedFilePath, configs, processedOptions);
- }
- exports.parse = parse;
- /**
- * Find all of the properties from matching sections in config files in the
- * same directory or toward the root of the filesystem. Synchronous.
- *
- * @param filepath The target file name, relative to process.cwd().
- * @param options All options
- * @returns Combined properties for the target file
- */
- function parseSync(filepath, options = {}) {
- const [resolvedFilePath, processedOptions] = opts(filepath, options);
- const filepaths = getConfigFileNames(resolvedFilePath, processedOptions);
- const configs = getAllConfigsSync(filepaths, processedOptions);
- return combine(resolvedFilePath, configs, processedOptions);
- }
- exports.parseSync = parseSync;
|