node/tools/eslint/node_modules/vfile/index.js
Rich Trott c5adb5f008
tools: update ESLint to 4.2.0
ESLint 4.2.0 contains a fix for a bug that is blocking us from moving to
the non-legacy stricter indentation linting. Update to 4.2.0 to remove
the blocking issue.

Backport-PR-URL: https://github.com/nodejs/node/pull/14859
PR-URL: https://github.com/nodejs/node/pull/14155
Ref: https://github.com/eslint/eslint/issues/8882
Ref: https://github.com/eslint/eslint/pull/8885
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Gibson Fahnestock <gibfahn@gmail.com>
Reviewed-By: Refael Ackermann <refack@gmail.com>
2017-09-05 12:49:53 -04:00

628 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @author Titus Wormer
* @copyright 2015 Titus Wormer
* @license MIT
* @module vfile
* @fileoverview Virtual file format to attach additional
* information related to processed input. Similar to
* `wearefractal/vinyl`. Additionally, `VFile` can be
* passed directly to ESLint formatters to visualise
* warnings and errors relating to a file.
* @example
* var VFile = require('vfile');
*
* var file = new VFile({
* 'directory': '~',
* 'filename': 'example',
* 'extension': 'txt',
* 'contents': 'Foo *bar* baz'
* });
*
* file.toString(); // 'Foo *bar* baz'
* file.filePath(); // '~/example.txt'
*
* file.move({'extension': 'md'});
* file.filePath(); // '~/example.md'
*
* file.warn('Something went wrong', {'line': 2, 'column': 3});
* // { [~/example.md:2:3: Something went wrong]
* // name: '~/example.md:2:3',
* // file: '~/example.md',
* // reason: 'Something went wrong',
* // line: 2,
* // column: 3,
* // fatal: false }
*/
'use strict';
/* eslint-env commonjs */
var proto;
var SEPARATOR = '/';
try {
SEPARATOR = require('pa' + 'th').sep;
} catch (e) { /* empty */ }
/**
* Construct a new file message.
*
* Note: We cannot invoke `Error` on the created context,
* as that adds readonly `line` and `column` attributes on
* Safari 9, thus throwing and failing the data.
*
* @example
* var message = new VFileMessage('Whoops!');
*
* message instanceof Error // true
*
* @constructor
* @class {VFileMessage}
* @param {string} reason - Reason for messaging.
* @property {boolean} [fatal=null] - Whether the message
* is fatal.
* @property {string} [name=''] - File-name and positional
* information.
* @property {string} [file=''] - File-path.
* @property {string} [reason=''] - Reason for messaging.
* @property {number} [line=null] - Start of message.
* @property {number} [column=null] - Start of message.
* @property {Position|Location} [location=null] - Place of
* message.
* @property {string} [stack] - Stack-trace of warning.
*/
function VFileMessage(reason) {
this.message = reason;
}
/**
* Inherit from `Error#`.
*/
function VFileMessagePrototype() {}
VFileMessagePrototype.prototype = Error.prototype;
proto = new VFileMessagePrototype();
VFileMessage.prototype = proto;
/*
* Expose defaults.
*/
proto.file = proto.name = proto.reason = proto.message = proto.stack = '';
proto.fatal = proto.column = proto.line = null;
/**
* File-related message with location information.
*
* @typedef {Error} VFileMessage
* @property {string} name - (Starting) location of the
* message, preceded by its file-path when available,
* and joined by `:`. Used internally by the native
* `Error#toString()`.
* @property {string} file - File-path.
* @property {string} reason - Reason for message.
* @property {number?} line - Line of message, when
* available.
* @property {number?} column - Column of message, when
* available.
* @property {string?} stack - Stack of message, when
* available.
* @property {boolean?} fatal - Whether the associated file
* is still processable.
*/
/**
* Stringify a position.
*
* @example
* stringify({'line': 1, 'column': 3}) // '1:3'
* stringify({'line': 1}) // '1:1'
* stringify({'column': 3}) // '1:3'
* stringify() // '1:1'
*
* @private
* @param {Object?} [position] - Single position, like
* those available at `node.position.start`.
* @return {string} - Compiled location.
*/
function stringify(position) {
if (!position) {
position = {};
}
return (position.line || 1) + ':' + (position.column || 1);
}
/**
* ESLint's formatter API expects `filePath` to be a
* string. This hack supports invocation as well as
* implicit coercion.
*
* @example
* var file = new VFile({
* 'filename': 'example',
* 'extension': 'txt'
* });
*
* filePath = filePathFactory(file);
*
* String(filePath); // 'example.txt'
* filePath(); // 'example.txt'
*
* @private
* @param {VFile} file - Virtual file.
* @return {Function} - `filePath` getter.
*/
function filePathFactory(file) {
/**
* Get the filename, with extension and directory, if applicable.
*
* @example
* var file = new VFile({
* 'directory': '~',
* 'filename': 'example',
* 'extension': 'txt'
* });
*
* String(file.filePath); // ~/example.txt
* file.filePath() // ~/example.txt
*
* @memberof {VFile}
* @property {Function} toString - Itself. ESLint's
* formatter API expects `filePath` to be `string`.
* This hack supports invocation as well as implicit
* coercion.
* @return {string} - If the `vFile` has a `filename`,
* it will be prefixed with the directory (slashed),
* if applicable, and suffixed with the (dotted)
* extension (if applicable). Otherwise, an empty
* string is returned.
*/
function filePath() {
var directory = file.directory;
var separator;
if (file.filename || file.extension) {
separator = directory.charAt(directory.length - 1);
if (separator === '/' || separator === '\\') {
directory = directory.slice(0, -1);
}
if (directory === '.') {
directory = '';
}
return (directory ? directory + SEPARATOR : '') +
file.filename +
(file.extension ? '.' + file.extension : '');
}
return '';
}
filePath.toString = filePath;
return filePath;
}
/**
* Get the filename with extantion.
*
* @example
* var file = new VFile({
* 'directory': '~/foo/bar'
* 'filename': 'example',
* 'extension': 'txt'
* });
*
* file.basename() // example.txt
*
* @memberof {VFile}
* @return {string} - name of file with extantion.
*/
function basename() {
var self = this;
var extension = self.extension;
if (self.filename || extension) {
return self.filename + (extension ? '.' + extension : '');
}
return '';
}
/**
* Construct a new file.
*
* @example
* var file = new VFile({
* 'directory': '~',
* 'filename': 'example',
* 'extension': 'txt',
* 'contents': 'Foo *bar* baz'
* });
*
* file === VFile(file) // true
* file === new VFile(file) // true
* VFile('foo') instanceof VFile // true
*
* @constructor
* @class {VFile}
* @param {Object|VFile|string} [options] - either an
* options object, or the value of `contents` (both
* optional). When a `file` is passed in, it's
* immediately returned.
* @property {string} [contents=''] - Content of file.
* @property {string} [directory=''] - Path to parent
* directory.
* @property {string} [filename=''] - Filename.
* A file-path can still be generated when no filename
* exists.
* @property {string} [extension=''] - Extension.
* A file-path can still be generated when no extension
* exists.
* @property {boolean?} quiet - Whether an error created by
* `VFile#fail()` is returned (when truthy) or thrown
* (when falsey). Ensure all `messages` associated with
* a file are handled properly when setting this to
* `true`.
* @property {Array.<VFileMessage>} messages - List of associated
* messages.
*/
function VFile(options) {
var self = this;
/*
* No `new` operator.
*/
if (!(self instanceof VFile)) {
return new VFile(options);
}
/*
* Given file.
*/
if (
options &&
typeof options.message === 'function' &&
typeof options.hasFailed === 'function'
) {
return options;
}
if (!options) {
options = {};
} else if (typeof options === 'string') {
options = {
'contents': options
};
}
self.contents = options.contents || '';
self.messages = [];
/*
* Make sure eslints formatters stringify `filePath`
* properly.
*/
self.filePath = filePathFactory(self);
self.history = [];
self.move({
'filename': options.filename,
'directory': options.directory,
'extension': options.extension
});
}
/**
* Get the value of the file.
*
* @example
* var vFile = new VFile('Foo');
* String(vFile); // 'Foo'
*
* @this {VFile}
* @memberof {VFile}
* @return {string} - value at the `contents` property
* in context.
*/
function toString() {
return this.contents;
}
/**
* Move a file by passing a new directory, filename,
* and extension. When these are not given, the default
* values are kept.
*
* @example
* var file = new VFile({
* 'directory': '~',
* 'filename': 'example',
* 'extension': 'txt',
* 'contents': 'Foo *bar* baz'
* });
*
* file.move({'directory': '/var/www'});
* file.filePath(); // '/var/www/example.txt'
*
* file.move({'extension': 'md'});
* file.filePath(); // '/var/www/example.md'
*
* @this {VFile}
* @memberof {VFile}
* @param {Object?} [options] - Configuration.
* @return {VFile} - Context object.
*/
function move(options) {
var self = this;
var before = self.filePath();
var after;
if (!options) {
options = {};
}
self.directory = options.directory || self.directory || '';
self.filename = options.filename || self.filename || '';
self.extension = options.extension || self.extension || '';
after = self.filePath();
if (after && before !== after) {
self.history.push(after);
}
return self;
}
/**
* Create a message with `reason` at `position`.
* When an error is passed in as `reason`, copies the
* stack. This does not add a message to `messages`.
*
* @example
* var file = new VFile();
*
* file.message('Something went wrong');
* // { [1:1: Something went wrong]
* // name: '1:1',
* // file: '',
* // reason: 'Something went wrong',
* // line: null,
* // column: null }
*
* @this {VFile}
* @memberof {VFile}
* @param {string|Error} reason - Reason for message.
* @param {Node|Location|Position} [position] - Location
* of message in file.
* @param {string} [ruleId] - Category of warning.
* @return {VFileMessage} - File-related message with
* location information.
*/
function message(reason, position, ruleId) {
var filePath = this.filePath();
var range;
var err;
var location = {
'start': {
'line': null,
'column': null
},
'end': {
'line': null,
'column': null
}
};
/*
* Node / location / position.
*/
if (position && position.position) {
position = position.position;
}
if (position && position.start) {
range = stringify(position.start) + '-' + stringify(position.end);
location = position;
position = position.start;
} else {
range = stringify(position);
if (position) {
location.start = position;
location.end.line = null;
location.end.column = null;
}
}
err = new VFileMessage(reason.message || reason);
err.name = (filePath ? filePath + ':' : '') + range;
err.file = filePath;
err.reason = reason.message || reason;
err.line = position ? position.line : null;
err.column = position ? position.column : null;
err.location = location;
err.ruleId = ruleId || null;
if (reason.stack) {
err.stack = reason.stack;
}
return err;
}
/**
* Warn. Creates a non-fatal message (see `VFile#message()`),
* and adds it to the file's `messages` list.
*
* @example
* var file = new VFile();
*
* file.warn('Something went wrong');
* // { [1:1: Something went wrong]
* // name: '1:1',
* // file: '',
* // reason: 'Something went wrong',
* // line: null,
* // column: null,
* // fatal: false }
*
* @see VFile#message
* @this {VFile}
* @memberof {VFile}
*/
function warn() {
var err = this.message.apply(this, arguments);
err.fatal = false;
this.messages.push(err);
return err;
}
/**
* Fail. Creates a fatal message (see `VFile#message()`),
* sets `fatal: true`, adds it to the file's
* `messages` list.
*
* If `quiet` is not `true`, throws the error.
*
* @example
* var file = new VFile();
*
* file.fail('Something went wrong');
* // 1:1: Something went wrong
* // at VFile.exception (vfile/index.js:296:11)
* // at VFile.fail (vfile/index.js:360:20)
* // at repl:1:6
*
* file.quiet = true;
* file.fail('Something went wrong');
* // { [1:1: Something went wrong]
* // name: '1:1',
* // file: '',
* // reason: 'Something went wrong',
* // line: null,
* // column: null,
* // fatal: true }
*
* @this {VFile}
* @memberof {VFile}
* @throws {VFileMessage} - When not `quiet: true`.
* @param {string|Error} reason - Reason for failure.
* @param {Node|Location|Position} [position] - Place
* of failure in file.
* @return {VFileMessage} - Unless thrown, of course.
*/
function fail(reason, position) {
var err = this.message(reason, position);
err.fatal = true;
this.messages.push(err);
if (!this.quiet) {
throw err;
}
return err;
}
/**
* Check if a fatal message occurred making the file no
* longer processable.
*
* @example
* var file = new VFile();
* file.quiet = true;
*
* file.hasFailed(); // false
*
* file.fail('Something went wrong');
* file.hasFailed(); // true
*
* @this {VFile}
* @memberof {VFile}
* @return {boolean} - `true` if at least one of file's
* `messages` has a `fatal` property set to `true`
*/
function hasFailed() {
var messages = this.messages;
var index = -1;
var length = messages.length;
while (++index < length) {
if (messages[index].fatal) {
return true;
}
}
return false;
}
/**
* Access metadata.
*
* @example
* var file = new VFile('Foo');
*
* file.namespace('foo').bar = 'baz';
*
* console.log(file.namespace('foo').bar) // 'baz';
*
* @this {VFile}
* @memberof {VFile}
* @param {string} key - Namespace key.
* @return {Object} - Private space.
*/
function namespace(key) {
var self = this;
var space = self.data;
if (!space) {
space = self.data = {};
}
if (!space[key]) {
space[key] = {};
}
return space[key];
}
/*
* Methods.
*/
proto = VFile.prototype;
proto.basename = basename;
proto.move = move;
proto.toString = toString;
proto.message = message;
proto.warn = warn;
proto.fail = fail;
proto.hasFailed = hasFailed;
proto.namespace = namespace;
/*
* Expose.
*/
module.exports = VFile;