Skip to content

Commit 4168238

Browse files
committed
Merge in upstream changes
2 parents b4143e1 + d6f0b34 commit 4168238

23 files changed

+482
-120
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
## v0.4.0
2+
* Support webpack
3+
14
## v0.3.0
25
* Add `vue` support
36

README.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,8 +210,7 @@ import Hello from '@/components/hello.vue'
210210
}
211211
```
212212

213-
6. The commonly used `@` path wildcard will work if you set up a `baseUrl` and `paths` (in `compilerOptions`) to include `@/*`. If you don't set this, then
214-
the fallback for the `@` wildcard will be `[tsconfig directory]/src` (we hope to make this more flexible on future releases):
213+
6. It accepts any wildcard in your TypeScript configuration:
215214
```
216215
// tsconfig.json
217216
{
@@ -223,6 +222,9 @@ the fallback for the `@` wildcard will be `[tsconfig directory]/src` (we hope to
223222
"paths": {
224223
"@/*": [
225224
"src/*"
225+
],
226+
"~/*": [
227+
"src/*"
226228
]
227229
}
228230
}

package.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "fork-ts-checker-webpack-plugin",
3-
"version": "0.3.0",
3+
"version": "0.4.0",
44
"description": "Runs typescript type checker and linter on separate process.",
55
"main": "lib/index.js",
66
"files": [
@@ -73,14 +73,14 @@
7373
"typescript": "^2.6.2",
7474
"vue": "^2.5.9",
7575
"vue-class-component": "^6.1.1",
76-
"vue-loader": "^13.5.0",
76+
"vue-loader": "^14.1.0",
7777
"vue-template-compiler": "^2.5.9",
7878
"webpack": "^4.0.0-beta.0"
7979
},
8080
"peerDependencies": {
81-
"tslint": "^5.0.0",
81+
"tslint": "^4.0.0 || ^5.0.0",
8282
"typescript": "^2.1.0",
83-
"webpack": "^2.3.0 || ^3.0.0"
83+
"webpack": "^2.3.0 || ^3.0.0 || ^4.0.0"
8484
},
8585
"dependencies": {
8686
"babel-code-frame": "^6.22.0",

src/VueProgram.ts

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,24 +31,43 @@ class VueProgram {
3131
}
3232

3333
/**
34-
* Since 99.9% of Vue projects use the wildcard '@/*', we only search for that in tsconfig CompilerOptions.paths.
34+
* Search for default wildcard or wildcard from options, we only search for that in tsconfig CompilerOptions.paths.
3535
* The path is resolved with thie given substitution and includes the CompilerOptions.baseUrl (if given).
3636
* If no paths given in tsconfig, then the default substitution is '[tsconfig directory]/src'.
3737
* (This is a fast, simplified inspiration of what's described here: https://github.com/Microsoft/TypeScript/issues/5039)
3838
*/
3939
public static resolveNonTsModuleName(moduleName: string, containingFile: string, basedir: string, options: ts.CompilerOptions) {
4040
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) === '@/';
41+
const discardedSymbols = ['.', '..', '/'];
42+
const wildcards: string[] = [];
43+
44+
if (options.paths) {
45+
Object.keys(options.paths).forEach(key => {
46+
const pathSymbol = key[0];
47+
if (discardedSymbols.indexOf(pathSymbol) < 0 && wildcards.indexOf(pathSymbol) < 0) {
48+
wildcards.push(pathSymbol);
49+
}
50+
});
51+
} else {
52+
wildcards.push('@');
53+
}
54+
4455
const isRelative = !path.isAbsolute(moduleName);
56+
let correctWildcard;
4557

46-
if (isWildcard) {
58+
wildcards.forEach(wildcard => {
59+
if (moduleName.substr(0, 2) === `${wildcard}/`) {
60+
correctWildcard = wildcard;
61+
}
62+
});
63+
64+
if (correctWildcard) {
65+
const pattern = options.paths ? options.paths[`${correctWildcard}/*`] : undefined;
66+
const substitution = pattern ? options.paths[`${correctWildcard}/*`][0].replace('*', '') : 'src';
4767
moduleName = path.resolve(baseUrl, substitution, moduleName.substr(2));
4868
} else if (isRelative) {
4969
moduleName = path.resolve(path.dirname(containingFile), moduleName);
5070
}
51-
5271
return moduleName;
5372
}
5473

@@ -66,6 +85,19 @@ class VueProgram {
6685
const host = ts.createCompilerHost(programConfig.options);
6786
const realGetSourceFile = host.getSourceFile;
6887

88+
const getScriptKind = (lang: string) => {
89+
if (lang === "ts") {
90+
return ts.ScriptKind.TS;
91+
} else if (lang === "tsx") {
92+
return ts.ScriptKind.TSX;
93+
} else if (lang === "jsx") {
94+
return ts.ScriptKind.JSX;
95+
} else {
96+
// when lang is "js" or no lang specified
97+
return ts.ScriptKind.JS;
98+
}
99+
}
100+
69101
// We need a host that can parse Vue SFCs (single file components).
70102
host.getSourceFile = (filePath, languageVersion, onError) => {
71103
// first check if watcher is watching file - if not - check it's mtime
@@ -91,8 +123,21 @@ class VueProgram {
91123

92124
// get typescript contents from Vue file
93125
if (source && VueProgram.isVue(filePath)) {
94-
const parsed = vueParser.parse(source.text, 'script', { lang: ['ts', 'tsx', 'js', 'jsx'] });
95-
source = ts.createSourceFile(filePath, parsed, languageVersion, true);
126+
let parsed: string;
127+
let kind: ts.ScriptKind;
128+
for (const lang of ['ts', 'tsx', 'js', 'jsx']) {
129+
parsed = vueParser.parse(source.text, 'script', { lang: [lang], emptyExport: false });
130+
if (parsed) {
131+
kind = getScriptKind(lang);
132+
break;
133+
}
134+
}
135+
if (!parsed) {
136+
// when script tag has no lang, or no script tag given
137+
parsed = vueParser.parse(source.text, 'script');
138+
kind = ts.ScriptKind.JS;
139+
}
140+
source = ts.createSourceFile(filePath, parsed, languageVersion, true, kind);
96141
}
97142

98143
return source;
@@ -104,13 +149,29 @@ class VueProgram {
104149

105150
for (const moduleName of moduleNames) {
106151
// Try to use standard resolution.
107-
const result = ts.resolveModuleName(moduleName, containingFile, programConfig.options, {
108-
fileExists: host.fileExists,
109-
readFile: host.readFile
152+
const { resolvedModule } = ts.resolveModuleName(moduleName, containingFile, programConfig.options, {
153+
fileExists(fileName) {
154+
if (fileName.endsWith('.vue.ts')) {
155+
return host.fileExists(fileName.slice(0, -3)) || host.fileExists(fileName);
156+
} else {
157+
return host.fileExists(fileName);
158+
}
159+
},
160+
readFile(fileName) {
161+
// This implementation is not necessary. Just for consistent behavior.
162+
if (fileName.endsWith('.vue.ts') && !host.fileExists(fileName)) {
163+
return host.readFile(fileName.slice(0, -3));
164+
} else {
165+
return host.readFile(fileName);
166+
}
167+
}
110168
});
111169

112-
if (result.resolvedModule) {
113-
resolvedModules.push(result.resolvedModule);
170+
if (resolvedModule) {
171+
if (resolvedModule.resolvedFileName.endsWith('vue.ts') && !host.fileExists(resolvedModule.resolvedFileName)) {
172+
resolvedModule.resolvedFileName = resolvedModule.resolvedFileName.slice(0, -3);
173+
}
174+
resolvedModules.push(resolvedModule);
114175
} else {
115176
// For non-ts extensions.
116177
const absolutePath = VueProgram.resolveNonTsModuleName(moduleName, containingFile, basedir, programConfig.options);

test/integration/vue.spec.js

Lines changed: 128 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ describe('[INTEGRATION] vue', function () {
135135
createCompiler({ tslint: true, vue: true });
136136

137137
compiler.run(function(error, stats) {
138-
expect(stats.compilation.errors.length).to.be.equal(2);
138+
expect(stats.compilation.errors.length).to.be.equal(1);
139139
callback();
140140
});
141141
});
@@ -144,8 +144,133 @@ describe('[INTEGRATION] vue', function () {
144144
createCompiler({ tslint: true, vue: true, checkSyntacticErrors: true });
145145

146146
compiler.run(function(error, stats) {
147-
expect(stats.compilation.errors.length).to.be.equal(3);
147+
expect(stats.compilation.errors.length).to.be.equal(2);
148148
callback();
149149
});
150150
});
151-
});
151+
152+
[
153+
'example-ts.vue',
154+
'example-tsx.vue',
155+
'example-js.vue',
156+
'example-jsx.vue',
157+
'example-nolang.vue'
158+
].forEach(fileName => {
159+
it('should be able to extract script from ' + fileName, function () {
160+
createCompiler({ vue: true, tsconfig: 'tsconfig-langs.json' });
161+
var sourceFilePath = path.resolve(compiler.context, 'src/langs/' + fileName)
162+
var source = checker.program.getSourceFile(sourceFilePath);
163+
expect(source).to.not.be.undefined;
164+
// remove padding lines
165+
var text = source.text.replace(/^\s*\/\/.*$\n/gm, '');
166+
expect(text.startsWith('/* OK */')).to.be.true;
167+
});
168+
});
169+
170+
function groupByFileName(errors) {
171+
var ret = {
172+
'example-ts.vue': [],
173+
'example-tsx.vue': [],
174+
'example-js.vue': [],
175+
'example-jsx.vue': [],
176+
'example-nolang.vue': []
177+
};
178+
for (var error of errors) {
179+
ret[path.basename(error.file)].push(error);
180+
}
181+
return ret;
182+
}
183+
184+
describe('should be able to compile *.vue with each lang', function () {
185+
var errors;
186+
before(function(callback) {
187+
createCompiler({ vue: true, tsconfig: 'tsconfig-langs.json' });
188+
compiler.run(function(error, stats) {
189+
errors = groupByFileName(stats.compilation.errors);
190+
callback();
191+
});
192+
});
193+
it("lang=ts", function() {
194+
expect(errors['example-ts.vue'].length).to.be.equal(0);
195+
})
196+
it("lang=tsx", function() {
197+
expect(errors['example-tsx.vue'].length).to.be.equal(0);
198+
});
199+
it("lang=js", function() {
200+
expect(errors['example-js.vue'].length).to.be.equal(0);
201+
});
202+
it("lang=jsx", function() {
203+
expect(errors['example-jsx.vue'].length).to.be.equal(0);
204+
});
205+
it("no lang", function() {
206+
expect(errors['example-nolang.vue'].length).to.be.equal(0);
207+
});
208+
});
209+
210+
describe('should be able to detect errors in *.vue', function () {
211+
var errors;
212+
before(function(callback) {
213+
// tsconfig-langs-strict.json === tsconfig-langs.json + noUnusedLocals
214+
createCompiler({ vue: true, tsconfig: 'tsconfig-langs-strict.json' });
215+
compiler.run(function(error, stats) {
216+
errors = groupByFileName(stats.compilation.errors);
217+
callback();
218+
});
219+
});
220+
it("lang=ts", function() {
221+
expect(errors['example-ts.vue'].length).to.be.equal(1);
222+
expect(errors['example-ts.vue'][0].rawMessage).to.match(/'a' is declared but/);
223+
})
224+
it("lang=tsx", function() {
225+
expect(errors['example-tsx.vue'].length).to.be.equal(1);
226+
expect(errors['example-tsx.vue'][0].rawMessage).to.match(/'a' is declared but/);
227+
});
228+
it("lang=js", function() {
229+
expect(errors['example-js.vue'].length).to.be.equal(0);
230+
});
231+
it("lang=jsx", function() {
232+
expect(errors['example-jsx.vue'].length).to.be.equal(0);
233+
});
234+
it("no lang", function() {
235+
expect(errors['example-nolang.vue'].length).to.be.equal(0);
236+
});
237+
});
238+
239+
describe('should resolve *.vue in the same way as TypeScript', function() {
240+
var errors;
241+
before(function(callback) {
242+
createCompiler({ vue: true, tsconfig: 'tsconfig-imports.json' });
243+
compiler.run(function(error, stats) {
244+
errors = stats.compilation.errors;
245+
callback();
246+
});
247+
});
248+
249+
it('should be able to import by relative path', function() {
250+
expect(
251+
errors.filter(e => e.rawMessage.indexOf('./Component1.vue') >= 0).length
252+
).to.be.equal(0);
253+
});
254+
it('should be able to import by path from baseUrl', function() {
255+
expect(
256+
errors.filter(e => e.rawMessage.indexOf('imports/Component2.vue') >= 0).length
257+
).to.be.equal(0);
258+
});
259+
it('should be able to import by compilerOptions.paths setting', function() {
260+
expect(
261+
errors.filter(e => e.rawMessage.indexOf('@/Component3.vue') >= 0).length
262+
).to.be.equal(0);
263+
});
264+
it('should be able to import by compilerOptions.paths setting (by array)', function() {
265+
expect(
266+
errors.filter(e => e.rawMessage.indexOf('foo/Foo1.vue') >= 0).length
267+
).to.be.equal(0);
268+
expect(
269+
errors.filter(e => e.rawMessage.indexOf('foo/Foo2.vue') >= 0).length
270+
).to.be.equal(0);
271+
});
272+
it('should not report any compilation errors', function() {
273+
expect(errors.length).to.be.equal(0);
274+
});
275+
});
276+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<div>This is Component1</div>
3+
</template>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<div>This is Component2</div>
3+
</template>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<div>This is Component3</div>
3+
</template>
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<script lang="ts">
2+
// resolve by relative path
3+
import Component1 from "./Component1.vue"; // imports/Component1.vue
4+
5+
// resolve by path from baseUrl(./src)
6+
import Component2 from "imports/Component2.vue"; // imports/Component2.vue
7+
8+
// resolve by compilerOptions.paths
9+
// { "@/*": ["imports/*"] }
10+
import Component3 from "@/Component3.vue"; // imports/Component3.vue
11+
12+
// resolve by compilerOptions.paths
13+
// { "foo/*": ["imports/foo1/*", "imports/foo2/*"] }
14+
import Foo1 from "foo/Foo1.vue"; // imports/foo1/Foo1.vue
15+
import Foo2 from "foo/Foo2.vue"; // imports/foo2/Foo2.vue
16+
17+
export default {
18+
name: "ImportTest",
19+
components: {
20+
Component1,
21+
Component2,
22+
Component3,
23+
Foo1,
24+
Foo2
25+
}
26+
};
27+
</script>
28+
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<template>
2+
<div>This is Foo1</div>
3+
</template>

0 commit comments

Comments
 (0)