-
Notifications
You must be signed in to change notification settings - Fork 35
/
utils.js
163 lines (149 loc) · 5.72 KB
/
utils.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
'use strict';
var Promise = require('bluebird');
var fs = require('fs-extra');
var path = require('path');
var jshintcli = require('jshint/src/cli');
var debug = require('debug')('jsxhint');
var os = require('os');
// Check map for copied support files (package.json, .jshintrc) for a speedup.
var checkedSupportFiles = {};
var utils = module.exports = {
/**
* Places all temp files here.
* @type {String}
*/
tmpdir: path.join((os.tmpdir || os.tmpDir)(), 'jsxhint', String(process.pid)),
/**
* Drain a readstream into a string.
* @param {ReadableStream} stream Readable stream.
* @promise {String} File contents.
*/
drainStream: function drainStream(stream) {
return new Promise(function(resolve, reject) {
// Drain stream
var source = '';
stream.on('data', function(chunk){
source += chunk;
});
stream.on('end', function() {
resolve(source);
});
stream.on('error', reject);
});
},
/**
* Transforms a parse error into an object with 'file', 'error' properties so it can be fed back into jshint.
* @param {Error} error Parsing error.
* @return {Object} Object with filename and error that JSHint can read.
*/
transformError: function transformError(fileName, error, opts){
var err = {
file: fileName,
error: {
line: error.lineNumber,
character: error.column,
reason: error.description,
code: 'E041'
}
};
if (opts['--babel'] || opts['--babel-experimental']) {
err.error.line = error.loc.line;
err.error.character = error.loc.column;
err.error.reason = error.stack.match(/.*:\s(.*)\s\(\d+:\d+\)/)[1];
}
return err;
},
/**
* Given a fileName, get a multi-platform compatible file path that can be appended to an existing filepath.
* This is not necessary on *nix but is important on Windows because absolutePath + absolutePath
* leads to the concatenation of a drive letter (`c:/`) which is a broken path.
* @param {String} fileName file name.
* @return {String} Cleaned file name.
*/
getCleanAbsolutePath: function getCleanAbsolutePath(fileName) {
return '/' + path.relative('/', path.resolve(fileName));
},
/**
* As of JSHint 2.5.0 there is a new jshintrc option, overrides. This lets you set glob patterns with separate
* rules. It's super useful, but it uses minimatch internally and doesn't match our temp files correctly.
* This function modifies the input patterns so they'll work right on our temp dirs.
* @param {Object} jshintrc .jshintrc contents.
* @return {Object} Modified .jshintrc contents.
*/
ensureJshintrcOverrides: function ensureJshintrcOverrides(jshintrc) {
if (!jshintrc.overrides) return jshintrc;
var base = path.join(utils.tmpdir, process.cwd());
jshintrc.overrides = Object.keys(jshintrc.overrides).reduce(function(memo, key) {
memo[path.join(base, key)] = jshintrc.overrides[key];
return memo;
}, {});
return jshintrc;
},
/**
* Find a config file, searching up from dir to the root, and copy it to the tmpdir. The
* JSHint CLI uses these to determine settings.
* We attempt to preserve the original folder structure inside the tmpdir
* so that we have no unexpected configuration file priority.
* @param {String} dir Basename
* @param {String} file Filename.
*/
copyConfig: function copyConfig(dir, file){
var filePath = path.resolve(dir, file);
if (checkedSupportFiles[filePath]) return;
// Indicate that this is copied already to prevent unnecessary file operations.
checkedSupportFiles[filePath] = true;
if (fs.existsSync(filePath)) {
var destination = path.join(utils.tmpdir, utils.getCleanAbsolutePath(filePath));
debug("Copying support file from %s to temp directory at %s.", filePath, destination);
if (file === '.jshintrc') {
try {
var jshintrc = jshintcli.loadConfig(filePath);
fs.writeJSONSync(destination, utils.ensureJshintrcOverrides(jshintrc));
} catch(e) {
console.error('Unable to parse .jshintrc file at %s. It must be valid JSON. Error: %s', filePath, e.message);
return;
}
} else {
fs.copySync(filePath, destination);
}
}
// Not found, keep looking up the root.
else {
var parent = path.resolve(dir, '..');
// Return null at the root, which is also when dir and its parent are the same.
if (dir === parent) return;
return copyConfig(parent, file);
}
},
/**
* Given a filename and contents, write to disk.
* @private
* @param {String} fileName File name.
* @param {String} contents File contents.
*/
createTempFile: function createTempFile(fileName, contents){
fileName = utils.getCleanAbsolutePath(fileName);
var fileTempDestination = path.join(utils.tmpdir, fileName);
// Write the file to the temp directory.
// outputFile is from fs-extra and will mkdirp() automatically.
fs.outputFileSync(fileTempDestination, contents);
// For every file, we need to go up the path to find .jshintrc and package.json files, since
// they can modify the lint.
var dir = path.dirname(fileName);
utils.copyConfig(dir, '.jshintrc');
utils.copyConfig(dir, 'package.json');
return fileTempDestination;
},
/**
* Given a list of filenames and their contents, write temporary files
* to disk.
* @private
* @param {Array} fileNames File names.
* @param {Array} fileContents File contents.
*/
createTempFiles: function createTempFiles(fileNames, fileContents){
return fileNames.map(function(fileName) {
return utils.createTempFile(fileName, fileContents[fileNames.indexOf(fileName)]);
});
}
};