You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
235 lines
11 KiB
235 lines
11 KiB
"use strict"; |
|
Object.defineProperty(exports, "__esModule", { value: true }); |
|
const fs = require("fs"); |
|
const path = require("path"); |
|
const resolution_1 = require("./resolution"); |
|
class VueProgram { |
|
static loadProgramConfig(typescript, configFile, compilerOptions) { |
|
const extraExtensions = ['vue']; |
|
const parseConfigHost = { |
|
fileExists: typescript.sys.fileExists, |
|
readFile: typescript.sys.readFile, |
|
useCaseSensitiveFileNames: typescript.sys.useCaseSensitiveFileNames, |
|
readDirectory: (rootDir, extensions, excludes, includes, depth) => { |
|
return typescript.sys.readDirectory(rootDir, extensions.concat(extraExtensions), excludes, includes, depth); |
|
} |
|
}; |
|
const tsconfig = typescript.readConfigFile(configFile, typescript.sys.readFile).config; |
|
tsconfig.compilerOptions = tsconfig.compilerOptions || {}; |
|
tsconfig.compilerOptions = Object.assign({}, tsconfig.compilerOptions, compilerOptions); |
|
const parsed = typescript.parseJsonConfigFileContent(tsconfig, parseConfigHost, path.dirname(configFile)); |
|
parsed.options.allowNonTsExtensions = true; |
|
return parsed; |
|
} |
|
/** |
|
* Search for default wildcard or wildcard from options, we only search for that in tsconfig CompilerOptions.paths. |
|
* The path is resolved with thie given substitution and includes the CompilerOptions.baseUrl (if given). |
|
* If no paths given in tsconfig, then the default substitution is '[tsconfig directory]/src'. |
|
* (This is a fast, simplified inspiration of what's described here: https://github.com/Microsoft/TypeScript/issues/5039) |
|
*/ |
|
static resolveNonTsModuleName(moduleName, containingFile, basedir, options) { |
|
const baseUrl = options.baseUrl ? options.baseUrl : basedir; |
|
const discardedSymbols = ['.', '..', '/']; |
|
const wildcards = []; |
|
if (options.paths) { |
|
Object.keys(options.paths).forEach(key => { |
|
const pathSymbol = key[0]; |
|
if (discardedSymbols.indexOf(pathSymbol) < 0 && |
|
wildcards.indexOf(pathSymbol) < 0) { |
|
wildcards.push(pathSymbol); |
|
} |
|
}); |
|
} |
|
else { |
|
wildcards.push('@'); |
|
} |
|
const isRelative = !path.isAbsolute(moduleName); |
|
let correctWildcard; |
|
wildcards.forEach(wildcard => { |
|
if (moduleName.substr(0, 2) === `${wildcard}/`) { |
|
correctWildcard = wildcard; |
|
} |
|
}); |
|
if (correctWildcard) { |
|
const pattern = options.paths |
|
? options.paths[`${correctWildcard}/*`] |
|
: undefined; |
|
const substitution = pattern |
|
? options.paths[`${correctWildcard}/*`][0].replace('*', '') |
|
: 'src'; |
|
moduleName = path.resolve(baseUrl, substitution, moduleName.substr(2)); |
|
} |
|
else if (isRelative) { |
|
moduleName = path.resolve(path.dirname(containingFile), moduleName); |
|
} |
|
return moduleName; |
|
} |
|
static isVue(filePath) { |
|
return path.extname(filePath) === '.vue'; |
|
} |
|
static createProgram(typescript, programConfig, basedir, files, watcher, oldProgram, userResolveModuleName, userResolveTypeReferenceDirective) { |
|
const host = typescript.createCompilerHost(programConfig.options); |
|
const realGetSourceFile = host.getSourceFile; |
|
const { resolveModuleName, resolveTypeReferenceDirective } = resolution_1.makeResolutionFunctions(userResolveModuleName, userResolveTypeReferenceDirective); |
|
host.resolveModuleNames = (moduleNames, containingFile) => { |
|
return moduleNames.map(moduleName => { |
|
return resolveModuleName(typescript, moduleName, containingFile, programConfig.options, host).resolvedModule; |
|
}); |
|
}; |
|
host.resolveTypeReferenceDirectives = (typeDirectiveNames, containingFile) => { |
|
return typeDirectiveNames.map(typeDirectiveName => { |
|
return resolveTypeReferenceDirective(typescript, typeDirectiveName, containingFile, programConfig.options, host).resolvedTypeReferenceDirective; |
|
}); |
|
}; |
|
// We need a host that can parse Vue SFCs (single file components). |
|
host.getSourceFile = (filePath, languageVersion, onError) => { |
|
// first check if watcher is watching file - if not - check it's mtime |
|
if (!watcher.isWatchingFile(filePath)) { |
|
try { |
|
const stats = fs.statSync(filePath); |
|
files.setMtime(filePath, stats.mtime.valueOf()); |
|
} |
|
catch (e) { |
|
// probably file does not exists |
|
files.remove(filePath); |
|
} |
|
} |
|
// get source file only if there is no source in files register |
|
if (!files.has(filePath) || !files.getData(filePath).source) { |
|
files.mutateData(filePath, data => { |
|
data.source = realGetSourceFile(filePath, languageVersion, onError); |
|
}); |
|
} |
|
let source = files.getData(filePath).source; |
|
// get typescript contents from Vue file |
|
if (source && VueProgram.isVue(filePath)) { |
|
const resolved = VueProgram.resolveScriptBlock(typescript, source.text); |
|
source = typescript.createSourceFile(filePath, resolved.content, languageVersion, true, resolved.scriptKind); |
|
} |
|
return source; |
|
}; |
|
// We need a host with special module resolution for Vue files. |
|
host.resolveModuleNames = (moduleNames, containingFile) => { |
|
const resolvedModules = []; |
|
for (const moduleName of moduleNames) { |
|
// Try to use standard resolution. |
|
const { resolvedModule } = typescript.resolveModuleName(moduleName, containingFile, programConfig.options, { |
|
fileExists(fileName) { |
|
if (fileName.endsWith('.vue.ts')) { |
|
return (host.fileExists(fileName.slice(0, -3)) || |
|
host.fileExists(fileName)); |
|
} |
|
else { |
|
return host.fileExists(fileName); |
|
} |
|
}, |
|
readFile(fileName) { |
|
// This implementation is not necessary. Just for consistent behavior. |
|
if (fileName.endsWith('.vue.ts') && !host.fileExists(fileName)) { |
|
return host.readFile(fileName.slice(0, -3)); |
|
} |
|
else { |
|
return host.readFile(fileName); |
|
} |
|
} |
|
}); |
|
if (resolvedModule) { |
|
if (resolvedModule.resolvedFileName.endsWith('.vue.ts') && |
|
!host.fileExists(resolvedModule.resolvedFileName)) { |
|
resolvedModule.resolvedFileName = resolvedModule.resolvedFileName.slice(0, -3); |
|
} |
|
resolvedModules.push(resolvedModule); |
|
} |
|
else { |
|
// For non-ts extensions. |
|
const absolutePath = VueProgram.resolveNonTsModuleName(moduleName, containingFile, basedir, programConfig.options); |
|
if (VueProgram.isVue(moduleName)) { |
|
resolvedModules.push({ |
|
resolvedFileName: absolutePath, |
|
extension: '.ts' |
|
}); |
|
} |
|
else { |
|
resolvedModules.push({ |
|
// If the file does exist, return an empty string (because we assume user has provided a ".d.ts" file for it). |
|
resolvedFileName: host.fileExists(absolutePath) |
|
? '' |
|
: absolutePath, |
|
extension: '.ts' |
|
}); |
|
} |
|
} |
|
} |
|
return resolvedModules; |
|
}; |
|
return typescript.createProgram(programConfig.fileNames, programConfig.options, host, oldProgram // re-use old program |
|
); |
|
} |
|
static getScriptKindByLang(typescript, lang) { |
|
if (lang === 'ts') { |
|
return typescript.ScriptKind.TS; |
|
} |
|
else if (lang === 'tsx') { |
|
return typescript.ScriptKind.TSX; |
|
} |
|
else if (lang === 'jsx') { |
|
return typescript.ScriptKind.JSX; |
|
} |
|
else { |
|
// when lang is "js" or no lang specified |
|
return typescript.ScriptKind.JS; |
|
} |
|
} |
|
static resolveScriptBlock(typescript, content) { |
|
// We need to import vue-template-compiler lazily because it cannot be included it |
|
// as direct dependency because it is an optional dependency of fork-ts-checker-webpack-plugin. |
|
// Since its version must not mismatch with user-installed Vue.js, |
|
// we should let the users install vue-template-compiler by themselves. |
|
let parser; |
|
try { |
|
// tslint:disable-next-line |
|
parser = require('vue-template-compiler'); |
|
} |
|
catch (err) { |
|
throw new Error('When you use `vue` option, make sure to install `vue-template-compiler`.'); |
|
} |
|
const { script } = parser.parseComponent(content, { |
|
pad: 'space' |
|
}); |
|
// No <script> block |
|
if (!script) { |
|
return { |
|
scriptKind: typescript.ScriptKind.JS, |
|
content: '/* tslint:disable */\nexport default {};\n' |
|
}; |
|
} |
|
const scriptKind = VueProgram.getScriptKindByLang(typescript, script.lang); |
|
// There is src attribute |
|
if (script.attrs.src) { |
|
// import path cannot be end with '.ts[x]' |
|
const src = script.attrs.src.replace(/\.tsx?$/i, ''); |
|
return { |
|
scriptKind, |
|
// For now, ignore the error when the src file is not found |
|
// since it will produce incorrect code location. |
|
// It's not a large problem since it's handled on webpack side. |
|
content: '/* tslint:disable */\n' + |
|
'// @ts-ignore\n' + |
|
`export { default } from '${src}';\n` + |
|
'// @ts-ignore\n' + |
|
`export * from '${src}';\n` |
|
}; |
|
} |
|
// Pad blank lines to retain diagnostics location |
|
// We need to prepend `//` for each line to avoid |
|
// false positive of no-consecutive-blank-lines TSLint rule |
|
const offset = content.slice(0, script.start).split(/\r?\n/g).length; |
|
const paddedContent = Array(offset).join('//\n') + script.content.slice(script.start); |
|
return { |
|
scriptKind, |
|
content: paddedContent |
|
}; |
|
} |
|
} |
|
exports.VueProgram = VueProgram; |
|
//# sourceMappingURL=VueProgram.js.map
|