This repository has been archived by the owner on Aug 7, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 36
/
main.js
204 lines (182 loc) · 6.52 KB
/
main.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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
'use babel';
/* @flow */
import Path from 'path';
/* eslint-disable import/extensions, import/no-extraneous-dependencies */
import { CompositeDisposable } from 'atom';
import type { TextEditor } from 'atom';
/* eslint-enable import/extensions, import/no-extraneous-dependencies */
// Dependencies
let helpers;
let atomlinter;
let Reporter;
function loadDeps() {
if (!helpers) {
helpers = require('./helpers');
}
if (!atomlinter) {
atomlinter = require('atom-linter');
}
if (!Reporter) {
Reporter = require('jshint-json');
}
}
module.exports = {
activate() {
this.idleCallbacks = new Set();
let depsCallbackID;
const installLinterJSHintDeps = () => {
this.idleCallbacks.delete(depsCallbackID);
if (!atom.inSpecMode()) {
require('atom-package-deps').install('linter-jshint');
}
loadDeps();
};
depsCallbackID = window.requestIdleCallback(installLinterJSHintDeps);
this.idleCallbacks.add(depsCallbackID);
this.scopes = [];
this.subscriptions = new CompositeDisposable();
const scopeEmbedded = 'source.js.embedded.html';
this.subscriptions.add(
atom.config.observe('linter-jshint.executablePath', (value) => {
if (value === '') {
this.executablePath = Path.join(__dirname, '..', 'node_modules', 'jshint', 'bin', 'jshint');
} else {
this.executablePath = value;
}
}),
atom.config.observe('linter-jshint.disableWhenNoJshintrcFileInPath', (value) => {
this.disableWhenNoJshintrcFileInPath = value;
}),
atom.config.observe('linter-jshint.jshintFileName', (value) => {
this.jshintFileName = value;
}),
atom.config.observe('linter-jshint.jshintignoreFilename', (value) => {
this.jshintignoreFilename = value;
}),
atom.config.observe('linter-jshint.lintInlineJavaScript', (value) => {
this.lintInlineJavaScript = value;
if (value) {
this.scopes.push(scopeEmbedded);
} else if (this.scopes.indexOf(scopeEmbedded) !== -1) {
this.scopes.splice(this.scopes.indexOf(scopeEmbedded), 1);
}
}),
atom.config.observe('linter-jshint.scopes', (value) => {
// NOTE: Subscriptions are created in the order given to add() so this
// is safe at the end.
// Remove any old scopes
this.scopes.splice(0, this.scopes.length);
// Add the current scopes
Array.prototype.push.apply(this.scopes, value);
// Re-check the embedded JS scope
if (this.lintInlineJavaScript && this.scopes.indexOf(scopeEmbedded) !== -1) {
this.scopes.push(scopeEmbedded);
}
}),
atom.commands.add('atom-text-editor', {
'linter-jshint:debug': async () => {
loadDeps();
const debugString = await helpers.generateDebugString();
const notificationOptions = { detail: debugString, dismissable: true };
atom.notifications.addInfo('linter-jshint:: Debugging information', notificationOptions);
},
}),
);
},
deactivate() {
this.idleCallbacks.forEach(callbackID => window.cancelIdleCallback(callbackID));
this.idleCallbacks.clear();
this.subscriptions.dispose();
},
provideLinter() {
return {
name: 'JSHint',
grammarScopes: this.scopes,
scope: 'file',
lintsOnChange: true,
lint: async (textEditor: TextEditor) => {
const results = [];
const filePath = textEditor.getPath();
const fileDir = Path.dirname(filePath);
const fileContents = textEditor.getText();
loadDeps();
const parameters = ['--reporter', Reporter, '--filename', filePath];
const configFile = await atomlinter.findCachedAsync(fileDir, this.jshintFileName);
if (configFile) {
if (this.jshintFileName !== '.jshintrc') {
parameters.push('--config', configFile);
}
} else if (this.disableWhenNoJshintrcFileInPath && !(await helpers.hasHomeConfig())) {
return results;
}
// JSHint completely ignores .jshintignore files for STDIN on it's own
// so we must re-implement the functionality
const ignoreFile = await atomlinter.findCachedAsync(fileDir, this.jshintignoreFilename);
if (ignoreFile) {
const isIgnored = await helpers.isIgnored(filePath, ignoreFile);
if (isIgnored) {
return [];
}
}
if (this.lintInlineJavaScript &&
textEditor.getGrammar().scopeName.indexOf('text.html') !== -1
) {
parameters.push('--extract', 'always');
}
parameters.push('-');
const execOpts = {
stdin: fileContents,
ignoreExitCode: true,
cwd: fileDir,
};
const result = await atomlinter.execNode(this.executablePath, parameters, execOpts);
if (textEditor.getText() !== fileContents) {
// File has changed since the lint was triggered, tell Linter not to update
return null;
}
let parsed;
try {
parsed = JSON.parse(result);
} catch (_) {
// eslint-disable-next-line no-console
console.error('[Linter-JSHint]', _, result);
atom.notifications.addWarning(
'[Linter-JSHint]',
{ detail: 'JSHint return an invalid response, check your console for more info' },
);
return results;
}
Object.keys(parsed.result).forEach((entryID) => {
let message;
const entry = parsed.result[entryID];
const { error } = entry;
const errorType = error.code.substr(0, 1);
let severity = 'info';
if (errorType === 'E') {
severity = 'error';
} else if (errorType === 'W') {
severity = 'warning';
}
const line = error.line > 0 ? error.line - 1 : 0;
const character = error.character > 0 ? error.character - 1 : 0;
try {
const position = atomlinter.generateRange(textEditor, line, character);
message = {
severity,
excerpt: `${error.code} - ${error.reason}`,
location: {
file: filePath,
position,
},
};
} catch (e) {
message = helpers.generateInvalidTrace(line, character, filePath, textEditor, error);
}
results.push(message);
});
// Make sure any invalid traces have resolved
return Promise.all(results);
},
};
},
};