Skip to content

Commit cda4341

Browse files
committed
add vue functionality, add tests
1 parent 02f2a08 commit cda4341

16 files changed

+461
-12
lines changed

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ jspm_packages
1717

1818
# Optional npm cache directory
1919
.npm
20+
package-lock.json
2021

2122
# Optional REPL history
2223
.node_repl_history
2324

24-
# IDEA directory
25+
# Editor directories and files
2526
.idea
27+
.vscode

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ deploy:
44
55
skip_cleanup: true
66
api_key:
7-
secure: RdoLwtLDz3PI8fhTg0wXeRuL9JAffNJSTJvNExk9Q+fcccBl8yaNU4AId5OcuDwO3pnb59nKv8fIidW9VzemZ8/eCqCBbCx4qzGFxN2uCB+aBwQ7Svu6GAJV0JIEgIXG+dkByRawd9hCIyvi6IvhIA/XbVbboV5ngjwSW317SA0lbznGHQmIT4o93+YpUuT+pkGC04eJGIH6cgAe61QGwN6WyGx8hfJ+K+OCksZ5A4RIeSkRHY4zRgmlGGtSwOutUPh3OMIdG40E0UXLn3MRdANzekMFSowMZ8aFliqhDsgk/zk8IC/X6OlPFSTAmO1IkKqZiJsr1rxmeJvC9GDxvRZ3Vyv3GncoteEQVjKFsYRlTvmEouECSxafl2aOqa1i2TqU8if5GjgB6mm+pTjDTzF4KB3QkEtRKgTuB47+EUc5zXIBrgIAb3sWu+QgaSFOreG49cGccCsU0viLe2WHbjzuNUP3IMO0x9tL/rm0RQmP3FjQSWlDg6VXYjJs5UcpiQLabgL3u4tso9aYprqTKihqfQkyfi0R5qH872ncGlASkzwbcZxiUeWau3yx46iZdckPNTAKw0afeodmr7aL6B+xEWHfZv9OBn0UE5I23jvIlI+wzyWHWUgeVikBx1ZZV4OqAHOTdEMHQivbir4eYGGAYtY6yxzLpyfD4g0xIzM=
7+
secure: J4q7IGixHsEp5of9FG/Zefit8ba6bV7T1I0yV8TBNQHfwBaTC344PBhx3jRg8Nhv04vzkzY+s2cJYIuyqlfEeZrsBcFTvPE9LowR/4cMCoFjteb/gDGtW2AFw2M5pfJ6L4PHXtigO5emWs7xeT3R8KxcQn3e2nzJcGXShc0N9vCCDe601MKJFco5svoHqx5cAmHoknIQDPatddob9VdOUvaRGZWWh7Pv8pdDHGMcUk18TL67p55vMDXL8LvtHlOJpg106P0hX8a3NGhD0Oq9jGgmS77JwAOel36gncxou8CbEXlzpNsH65gJ2/tEJ3uSUZdHYQtQCiQtNzFbqI45JTu7UXkwgQv0Wxb7fnnNsz9StYmquVNp7MDw+Ol2/txA55tBBdSCeK+hy/X7cgI4tI/ftZ98c0YQbeUe/fdaV6PMxv0lPzED2We3N2Y080ineOib1ySsjY0diN2NM6NB8+9TJgqyLzd7X3WofJxExMFMRKuApCDCU7Bjw/FlKkISzvUOMmthL91LunQZ4x+ratiYVbz2cKa7PMcP0Qm7cFnosbyQKJWjM1KTOsCnoCS7iLZHxzKKE+prOWrssbcfENvpSTmouWrGppi9D8nwXDEFNyp+pVx8b7gwQ5GR/WjXb1PY/WkjBgNRaZbHzJlJzYTMBjNjlI1Nn0s4ncWhsZE=
88
on:
99
tags: true
1010
branch: master

package.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,10 @@
5050
"@types/lodash.startswith": "^4.2.3",
5151
"@types/minimatch": "^3.0.1",
5252
"@types/node": "^8.0.26",
53+
"@types/resolve": "0.0.4",
5354
"@types/webpack": "^3.0.10",
5455
"chai": "^3.5.0",
56+
"css-loader": "^0.28.7",
5557
"eslint": "^3.19.0",
5658
"istanbul": "^0.4.5",
5759
"mocha": "^3.4.1",
@@ -62,6 +64,10 @@
6264
"ts-loader": "^2.1.0",
6365
"tslint": "^5.0.0",
6466
"typescript": "^2.1.0",
67+
"vue": "^2.5.9",
68+
"vue-class-component": "^6.1.1",
69+
"vue-loader": "^13.5.0",
70+
"vue-template-compiler": "^2.5.9",
6571
"webpack": "^3.0.0"
6672
},
6773
"peerDependencies": {
@@ -76,6 +82,8 @@
7682
"lodash.isfunction": "^3.0.8",
7783
"lodash.isstring": "^4.0.1",
7884
"lodash.startswith": "^4.2.1",
79-
"minimatch": "^3.0.4"
85+
"minimatch": "^3.0.4",
86+
"resolve": "^1.5.0",
87+
"vue-parser": "^1.1.3"
8088
}
8189
}

src/IncrementalChecker.ts

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import WorkSet = require('./WorkSet');
99
import NormalizedMessage = require('./NormalizedMessage');
1010
import CancellationToken = require('./CancellationToken');
1111
import minimatch = require('minimatch');
12+
import VueProgram = require('./VueProgram');
1213

1314
// Need some augmentation here - linterOptions.exclude is not (yet) part of the official
1415
// types for tslint.
@@ -36,20 +37,24 @@ class IncrementalChecker {
3637
programConfig: ts.ParsedCommandLine;
3738
watcher: FilesWatcher;
3839

40+
vue: boolean;
41+
3942
constructor(
4043
programConfigFile: string,
4144
linterConfigFile: string | false,
4245
watchPaths: string[],
4346
workNumber: number,
4447
workDivision: number,
45-
checkSyntacticErrors: boolean
48+
checkSyntacticErrors: boolean,
49+
vue: boolean
4650
) {
4751
this.programConfigFile = programConfigFile;
4852
this.linterConfigFile = linterConfigFile;
4953
this.watchPaths = watchPaths;
5054
this.workNumber = workNumber || 0;
5155
this.workDivision = workDivision || 1;
5256
this.checkSyntacticErrors = checkSyntacticErrors || false;
57+
this.vue = vue || false;
5358
// Use empty array of exclusions in general to avoid having
5459
// to check of its existence later on.
5560
this.linterExclusions = [];
@@ -130,7 +135,8 @@ class IncrementalChecker {
130135

131136
nextIteration() {
132137
if (!this.watcher) {
133-
this.watcher = new FilesWatcher(this.watchPaths, ['.ts', '.tsx']);
138+
const watchExtensions = this.vue ? ['.ts', '.tsx', '.vue'] : ['.ts', '.tsx'];
139+
this.watcher = new FilesWatcher(this.watchPaths, watchExtensions);
134140

135141
// connect watcher with register
136142
this.watcher.on('change', (filePath: string, stats: fs.Stats) => {
@@ -143,10 +149,6 @@ class IncrementalChecker {
143149
this.watcher.watch();
144150
}
145151

146-
if (!this.programConfig) {
147-
this.programConfig = IncrementalChecker.loadProgramConfig(this.programConfigFile);
148-
}
149-
150152
if (!this.linterConfig && this.linterConfigFile) {
151153
this.linterConfig = IncrementalChecker.loadLinterConfig(this.linterConfigFile);
152154

@@ -158,12 +160,31 @@ class IncrementalChecker {
158160
}
159161
}
160162

161-
this.program = IncrementalChecker.createProgram(this.programConfig, this.files, this.watcher, this.program);
163+
this.program = this.vue ? this.loadVueProgram() : this.loadDefaultProgram();
164+
162165
if (this.linterConfig) {
163166
this.linter = IncrementalChecker.createLinter(this.program);
164167
}
165168
}
166169

170+
loadVueProgram() {
171+
this.programConfig = this.programConfig || VueProgram.loadProgramConfig(this.programConfigFile);
172+
173+
return VueProgram.createProgram(
174+
this.programConfig,
175+
path.dirname(this.programConfigFile),
176+
this.files,
177+
this.watcher,
178+
this.program
179+
);
180+
}
181+
182+
loadDefaultProgram() {
183+
this.programConfig = this.programConfig || IncrementalChecker.loadProgramConfig(this.programConfigFile);
184+
185+
return IncrementalChecker.createProgram(this.programConfig, this.files, this.watcher, this.program);
186+
}
187+
167188
hasLinter() {
168189
return this.linter !== undefined;
169190
}

src/VueProgram.ts

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import fs = require('fs');
2+
import path = require('path');
3+
import ts = require('typescript');
4+
import FilesRegister = require('./FilesRegister');
5+
import FilesWatcher = require('./FilesWatcher');
6+
import vueParser = require('vue-parser');
7+
8+
class VueProgram {
9+
static loadProgramConfig(configFile: string) {
10+
const extraExtensions = ['vue'];
11+
12+
const parseConfigHost: ts.ParseConfigHost = {
13+
fileExists: ts.sys.fileExists,
14+
readFile: ts.sys.readFile,
15+
useCaseSensitiveFileNames: ts.sys.useCaseSensitiveFileNames,
16+
readDirectory: (rootDir, extensions, excludes, includes, depth) => {
17+
return ts.sys.readDirectory(rootDir, extensions.concat(extraExtensions), excludes, includes, depth);
18+
}
19+
};
20+
21+
const parsed = ts.parseJsonConfigFileContent(
22+
// Regardless of the setting in the tsconfig.json we want isolatedModules to be false
23+
Object.assign(ts.readConfigFile(configFile, ts.sys.readFile).config, { isolatedModules: false }),
24+
parseConfigHost,
25+
path.dirname(configFile)
26+
);
27+
28+
parsed.options.allowNonTsExtensions = true;
29+
30+
return parsed;
31+
}
32+
33+
/**
34+
* Since 99.9% of Vue projects use the wildcard '@/*', we only search for that in tsconfig CompilerOptions.paths.
35+
* The path is resolved with thie given substitution and includes the CompilerOptions.baseUrl (if given).
36+
* If no paths given in tsconfig, then the default substitution is '[tsconfig directory]/src'.
37+
* (This is a fast, simplified inspiration of what's described here: https://github.com/Microsoft/TypeScript/issues/5039)
38+
*/
39+
public static resolveNonTsModuleName(moduleName: string, containingFile: string, basedir: string, options: ts.CompilerOptions) {
40+
const baseUrl = options.baseUrl ? options.baseUrl : basedir;
41+
const pattern = options.paths ? options.paths['@/*'] : undefined;
42+
const substitution = pattern ? options.paths['@/*'][0].replace('*', '') : 'src';
43+
const isWildcard = moduleName.substr(0, 2) === '@/';
44+
const isRelative = !path.isAbsolute(moduleName);
45+
46+
if (isWildcard) {
47+
moduleName = path.resolve(baseUrl, substitution, moduleName.substr(2));
48+
} else if (isRelative) {
49+
moduleName = path.resolve(path.dirname(containingFile), moduleName);
50+
}
51+
52+
return moduleName;
53+
}
54+
55+
static createProgram(
56+
programConfig: ts.ParsedCommandLine,
57+
basedir: string,
58+
files: FilesRegister,
59+
watcher: FilesWatcher,
60+
oldProgram: ts.Program
61+
) {
62+
const host = ts.createCompilerHost(programConfig.options);
63+
const realGetSourceFile = host.getSourceFile;
64+
65+
// We need a host that can parse Vue SFCs (single file components).
66+
host.getSourceFile = (filePath, languageVersion, onError) => {
67+
// first check if watcher is watching file - if not - check it's mtime
68+
if (!watcher.isWatchingFile(filePath)) {
69+
try {
70+
const stats = fs.statSync(filePath);
71+
72+
files.setMtime(filePath, stats.mtime.valueOf());
73+
} catch (e) {
74+
// probably file does not exists
75+
files.remove(filePath);
76+
}
77+
}
78+
79+
// get source file only if there is no source in files register
80+
if (!files.has(filePath) || !files.getData(filePath).source) {
81+
files.mutateData(filePath, (data) => {
82+
data.source = realGetSourceFile(filePath, languageVersion, onError);
83+
});
84+
}
85+
86+
let source = files.getData(filePath).source;
87+
88+
// get typescript contents from Vue file
89+
if (source && filePath.substr(-4) === '.vue') {
90+
const parsed = vueParser.parse(source.text, 'script', { lang: ['ts', 'tsx', 'js', 'jsx'] });
91+
source = ts.createSourceFile(filePath, parsed, languageVersion, true);
92+
}
93+
94+
return source;
95+
};
96+
97+
// We need a host with special module resolution for Vue files.
98+
host.resolveModuleNames = (moduleNames, containingFile) => {
99+
const resolvedModules: ts.ResolvedModule[] = [];
100+
101+
for (const moduleName of moduleNames) {
102+
// Try to use standard resolution.
103+
const result = ts.resolveModuleName(moduleName, containingFile, programConfig.options, {
104+
fileExists: host.fileExists,
105+
readFile: host.readFile
106+
});
107+
108+
if (result.resolvedModule) {
109+
resolvedModules.push(result.resolvedModule);
110+
} else {
111+
// For non-ts extensions.
112+
resolvedModules.push({
113+
resolvedFileName: VueProgram.resolveNonTsModuleName(moduleName, containingFile, basedir, programConfig.options),
114+
extension: '.ts'
115+
} as ts.ResolvedModuleFull);
116+
}
117+
}
118+
119+
return resolvedModules;
120+
};
121+
122+
return ts.createProgram(
123+
programConfig.fileNames,
124+
programConfig.options,
125+
host,
126+
oldProgram // re-use old program
127+
);
128+
}
129+
}
130+
131+
export = VueProgram;

src/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ interface Options {
3030
checkSyntacticErrors: boolean;
3131
memoryLimit: number;
3232
workers: number;
33+
vue: boolean;
3334
}
3435

3536
/**
@@ -84,6 +85,8 @@ class ForkTsCheckerWebpackPlugin {
8485

8586
service: childProcess.ChildProcess;
8687

88+
vue: boolean;
89+
8790
constructor(options: Options) {
8891
options = options || {} as Options;
8992
this.options = Object.assign({}, options);
@@ -126,6 +129,8 @@ class ForkTsCheckerWebpackPlugin {
126129

127130
this.typescriptVersion = require('typescript').version;
128131
this.tslintVersion = this.tslint ? require('tslint').Linter.VERSION : undefined;
132+
133+
this.vue = options.vue === true; // default false
129134
}
130135

131136
static createFormatter(type: 'default' | 'codeframe', options: any) {
@@ -313,7 +318,8 @@ class ForkTsCheckerWebpackPlugin {
313318
WATCH: this.isWatching ? this.watchPaths.join('|') : '',
314319
WORK_DIVISION: Math.max(1, this.workersNumber),
315320
MEMORY_LIMIT: this.memoryLimit,
316-
CHECK_SYNTACTIC_ERRORS: this.checkSyntacticErrors
321+
CHECK_SYNTACTIC_ERRORS: this.checkSyntacticErrors,
322+
VUE: this.vue
317323
}
318324
),
319325
stdio: ['inherit', 'inherit', 'inherit', 'ipc']

src/service.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ const checker = new IncrementalChecker(
1010
process.env.WATCH === '' ? [] : process.env.WATCH.split('|'),
1111
parseInt(process.env.WORK_NUMBER, 10),
1212
parseInt(process.env.WORK_DIVISION, 10),
13-
process.env.CHECK_SYNTACTIC_ERRORS === 'true'
13+
process.env.CHECK_SYNTACTIC_ERRORS === 'true',
14+
process.env.VUE === 'true'
1415
);
1516

1617
function run(cancellationToken: CancellationToken) {

test/integration/index.spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,8 +218,10 @@ describe('[INTEGRATION] index', function () {
218218

219219
it('should find syntactic errors when checkSyntacticErrors is true', function (callback) {
220220
var compiler = createCompiler({ checkSyntacticErrors: true }, true);
221+
var logger = console;
221222

222223
compiler.run(function(error, stats) {
224+
// logger.info(stats.compilation.errors);
223225
expect(stats.compilation.errors.length).to.be.equal(2);
224226
callback();
225227
});

0 commit comments

Comments
 (0)