From c3cea1ced81e4b013d19d689e1901effd5b41870 Mon Sep 17 00:00:00 2001 From: John Reilly Date: Sun, 23 Jul 2017 20:13:01 +0100 Subject: [PATCH] strict null fork (#589) * initial strict null commit * whoops * Fixup * less use of ! operator * Start losing !! * test for undefined rather than falsy checks * Global ignore * switch to importing interfaces explicitly * Move to es6 imports * directly import most required functions * tweak --- package-lock.json | 2 +- src/after-compile.ts | 95 ++++++++------- src/compilerSetup.ts | 24 ++-- src/config.ts | 37 +++--- src/index.ts | 113 +++++++++++------- src/instances.ts | 110 ++++++++++------- src/interfaces.ts | 22 ++-- src/logger.ts | 16 +-- src/resolver.ts | 8 +- src/servicesHost.ts | 86 ++++++------- src/tsconfig.json | 2 +- src/utils.ts | 98 ++++++++------- src/watch-run.ts | 32 ++--- .../create-and-execute-test.js | 7 +- .../expectedOutput-2.4/bundle.js | 2 +- .../expectedOutput-2.4/output.txt | 9 +- 16 files changed, 369 insertions(+), 294 deletions(-) diff --git a/package-lock.json b/package-lock.json index b997fb9a7..66021cf10 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "ts-loader", - "version": "2.3.0", + "version": "2.3.1", "lockfileVersion": 1, "dependencies": { "@types/chalk": { diff --git a/src/after-compile.ts b/src/after-compile.ts index 3dace35d6..68a887b84 100644 --- a/src/after-compile.ts +++ b/src/after-compile.ts @@ -1,17 +1,24 @@ -import interfaces = require('./interfaces'); -import path = require('path'); -import typescript = require('typescript'); -import utils = require('./utils'); -import constants = require('./constants'); - -function makeAfterCompile( - instance: interfaces.TSInstance, - configFilePath: string +import * as path from 'path'; +import * as typescript from 'typescript'; + +import { collectAllDependants, formatErrors, hasOwnProperty, registerWebpackErrors } from './utils'; +import * as constants from './constants'; +import { + TSFiles, + TSInstance, + WebpackCompilation, + WebpackError, + WebpackModule +} from './interfaces'; + +export function makeAfterCompile( + instance: TSInstance, + configFilePath: string | undefined ) { let getCompilerOptionDiagnostics = true; let checkAllFilesForErrors = true; - return (compilation: interfaces.WebpackCompilation, callback: () => void) => { + return (compilation: WebpackCompilation, callback: () => void) => { // Don't add errors for child compilations if (compilation.compiler.isChild()) { callback(); @@ -28,10 +35,10 @@ function makeAfterCompile( const filesToCheckForErrors = determineFilesToCheckForErrors(checkAllFilesForErrors, instance); checkAllFilesForErrors = false; - const filesWithErrors: interfaces.TSFiles = {}; + const filesWithErrors: TSFiles = {}; provideErrorsToWebpack(filesToCheckForErrors, filesWithErrors, compilation, modules, instance); - provideDeclarationFilesToWebpack(filesToCheckForErrors, instance.languageService, compilation); + provideDeclarationFilesToWebpack(filesToCheckForErrors, instance.languageService!, compilation); instance.filesWithErrors = filesWithErrors; instance.modifiedFiles = null; @@ -40,7 +47,7 @@ function makeAfterCompile( } interface Modules { - [modulePath: string]: interfaces.WebpackModule[]; + [modulePath: string]: WebpackModule[]; } /** @@ -48,16 +55,16 @@ interface Modules { */ function provideCompilerOptionDiagnosticErrorsToWebpack( getCompilerOptionDiagnostics: boolean, - compilation: interfaces.WebpackCompilation, - instance: interfaces.TSInstance, - configFilePath: string + compilation: WebpackCompilation, + instance: TSInstance, + configFilePath: string | undefined ) { if (getCompilerOptionDiagnostics) { const { languageService, loaderOptions, compiler } = instance; - utils.registerWebpackErrors( + registerWebpackErrors( compilation.errors, - utils.formatErrors( - languageService.getCompilerOptionsDiagnostics(), + formatErrors( + languageService!.getCompilerOptionsDiagnostics(), loaderOptions, compiler, { file: configFilePath || 'tsconfig.json' })); } @@ -69,13 +76,13 @@ function provideCompilerOptionDiagnosticErrorsToWebpack( * based on filepath */ function determineModules( - compilation: interfaces.WebpackCompilation + compilation: WebpackCompilation ) { const modules: Modules = {}; compilation.modules.forEach(module => { if (module.resource) { const modulePath = path.normalize(module.resource); - if (utils.hasOwnProperty(modules, modulePath)) { + if (hasOwnProperty(modules, modulePath)) { const existingModules = modules[modulePath]; if (existingModules.indexOf(module) === -1) { existingModules.push(module); @@ -90,18 +97,18 @@ function determineModules( function determineFilesToCheckForErrors( checkAllFilesForErrors: boolean, - instance: interfaces.TSInstance + instance: TSInstance ) { const { files, modifiedFiles, filesWithErrors } = instance // calculate array of files to check - let filesToCheckForErrors: interfaces.TSFiles = {}; + let filesToCheckForErrors: TSFiles = {}; if (checkAllFilesForErrors) { // check all files on initial run filesToCheckForErrors = files; - } else if (modifiedFiles) { + } else if (modifiedFiles !== null && modifiedFiles !== undefined) { // check all modified files, and all dependants Object.keys(modifiedFiles).forEach(modifiedFileName => { - utils.collectAllDependants(instance.reverseDependencyGraph, modifiedFileName) + collectAllDependants(instance.reverseDependencyGraph, modifiedFileName) .forEach(fileName => { filesToCheckForErrors[fileName] = files[fileName]; }); @@ -109,7 +116,7 @@ function determineFilesToCheckForErrors( } // re-check files with errors from previous build - if (filesWithErrors) { + if (filesWithErrors !== undefined) { Object.keys(filesWithErrors).forEach(fileWithErrorName => filesToCheckForErrors[fileWithErrorName] = filesWithErrors[fileWithErrorName] ); @@ -118,26 +125,26 @@ function determineFilesToCheckForErrors( } function provideErrorsToWebpack( - filesToCheckForErrors: interfaces.TSFiles, - filesWithErrors: interfaces.TSFiles, - compilation: interfaces.WebpackCompilation, + filesToCheckForErrors: TSFiles, + filesWithErrors: TSFiles, + compilation: WebpackCompilation, modules: Modules, - instance: interfaces.TSInstance + instance: TSInstance ) { const { compiler, languageService, files, loaderOptions, compilerOptions } = instance; let filePathRegex = !!compilerOptions.checkJs ? constants.dtsTsTsxJsJsxRegex : constants.dtsTsTsxRegex; Object.keys(filesToCheckForErrors) - .filter(filePath => !!filePath.match(filePathRegex)) + .filter(filePath => filePath.match(filePathRegex)) .forEach(filePath => { - const errors = languageService.getSyntacticDiagnostics(filePath).concat(languageService.getSemanticDiagnostics(filePath)); + const errors = languageService!.getSyntacticDiagnostics(filePath).concat(languageService!.getSemanticDiagnostics(filePath)); if (errors.length > 0) { filesWithErrors[filePath] = files[filePath]; } // if we have access to a webpack module, use that - if (utils.hasOwnProperty(modules, filePath)) { + if (hasOwnProperty(modules, filePath)) { const associatedModules = modules[filePath]; associatedModules.forEach(module => { @@ -145,13 +152,13 @@ function provideErrorsToWebpack( removeTSLoaderErrors(module.errors); // append errors - const formattedErrors = utils.formatErrors(errors, loaderOptions, compiler, { module }); - utils.registerWebpackErrors(module.errors, formattedErrors); - utils.registerWebpackErrors(compilation.errors, formattedErrors); + const formattedErrors = formatErrors(errors, loaderOptions, compiler, { module }); + registerWebpackErrors(module.errors, formattedErrors); + registerWebpackErrors(compilation.errors, formattedErrors); }); } else { // otherwise it's a more generic error - utils.registerWebpackErrors(compilation.errors, utils.formatErrors(errors, loaderOptions, compiler, { file: filePath })); + registerWebpackErrors(compilation.errors, formatErrors(errors, loaderOptions, compiler, { file: filePath })); } }); } @@ -160,16 +167,16 @@ function provideErrorsToWebpack( * gather all declaration files from TypeScript and output them to webpack */ function provideDeclarationFilesToWebpack( - filesToCheckForErrors: interfaces.TSFiles, + filesToCheckForErrors: TSFiles, languageService: typescript.LanguageService, - compilation: interfaces.WebpackCompilation + compilation: WebpackCompilation ) { Object.keys(filesToCheckForErrors) - .filter(filePath => !!filePath.match(constants.tsTsxRegex)) + .filter(filePath => filePath.match(constants.tsTsxRegex)) .forEach(filePath => { const output = languageService.getEmitOutput(filePath); - const declarationFile = output.outputFiles.filter(outputFile => !!outputFile.name.match(constants.dtsDtsxRegex)).pop(); - if (declarationFile) { + const declarationFile = output.outputFiles.filter(outputFile => outputFile.name.match(constants.dtsDtsxRegex)).pop(); + if (declarationFile !== undefined) { const assetPath = path.relative(compilation.compiler.context, declarationFile.name); compilation.assets[assetPath] = { source: () => declarationFile.text, @@ -186,7 +193,7 @@ function provideDeclarationFilesToWebpack( * compilation-to-compilation, and since not every module always runs through * the loader, we need to detect and remove any pre-existing errors. */ -function removeTSLoaderErrors(errors: interfaces.WebpackError[]) { +function removeTSLoaderErrors(errors: WebpackError[]) { let index = -1; let length = errors.length; while (++index < length) { @@ -196,5 +203,3 @@ function removeTSLoaderErrors(errors: interfaces.WebpackError[]) { } } } - -export = makeAfterCompile; diff --git a/src/compilerSetup.ts b/src/compilerSetup.ts index 1b8b640a0..4882f6bcd 100644 --- a/src/compilerSetup.ts +++ b/src/compilerSetup.ts @@ -1,18 +1,18 @@ -import typescript = require('typescript'); +import * as typescript from 'typescript'; const semver = require('semver'); -import interfaces = require('./interfaces'); -import constants = require('./constants'); -import logger = require('./logger'); +import * as constants from './constants'; +import * as logger from './logger'; import { red, yellow } from 'chalk'; +import { LoaderOptions } from './interfaces'; export function getCompiler( - loaderOptions: interfaces.LoaderOptions, + loaderOptions: LoaderOptions, log: logger.Logger ) { - let compiler: typeof typescript; - let errorMessage: string; - let compilerDetailsLogMessage: string; + let compiler: typeof typescript | undefined; + let errorMessage: string | undefined; + let compilerDetailsLogMessage: string | undefined; let compilerCompatible = false; try { @@ -23,11 +23,11 @@ export function getCompiler( : `Could not load TypeScript compiler with NPM package name \`${loaderOptions.compiler}\`. Are you sure it is correctly installed?`; } - if (!errorMessage) { - compilerDetailsLogMessage = `ts-loader: Using ${loaderOptions.compiler}@${compiler.version}`; + if (errorMessage === undefined) { + compilerDetailsLogMessage = `ts-loader: Using ${loaderOptions.compiler}@${compiler!.version}`; compilerCompatible = false; if (loaderOptions.compiler === 'typescript') { - if (compiler.version && semver.gte(compiler.version, '1.6.2-0')) { + if (compiler!.version && semver.gte(compiler!.version, '1.6.2-0')) { // don't log yet in this case, if a tsconfig.json exists we want to combine the message compilerCompatible = true; } else { @@ -52,7 +52,7 @@ export function getCompilerOptions( }); // if `module` is not specified and not using ES6 target, default to CJS module output - if ((!compilerOptions.module) && compilerOptions.target !== constants.ScriptTargetES2015) { + if ((compilerOptions.module === undefined) && compilerOptions.target !== constants.ScriptTargetES2015) { compilerOptions.module = constants.ModuleKindCommonJs; } else if (compilerCompatible && semver.lt(compiler.version, '1.7.3-0') && compilerOptions.target === constants.ScriptTargetES2015) { // special handling for TS 1.6 and target: es6 diff --git a/src/config.ts b/src/config.ts index 5ce7f7dc4..b9fe5c0e6 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,9 +1,14 @@ -import typescript = require('typescript'); -import path = require('path'); +import * as typescript from 'typescript'; +import * as path from 'path'; -import interfaces = require('./interfaces'); -import logger = require('./logger'); -import utils = require('./utils'); +import * as logger from './logger'; +import { formatErrors } from './utils'; +import { + LoaderOptions, + TSCompatibleCompiler, + Webpack, + WebpackError +} from './interfaces'; import { green } from 'chalk'; interface ConfigFile { @@ -13,17 +18,17 @@ interface ConfigFile { export function getConfigFile( compiler: typeof typescript, - loader: interfaces.Webpack, - loaderOptions: interfaces.LoaderOptions, + loader: Webpack, + loaderOptions: LoaderOptions, compilerCompatible: boolean, log: logger.Logger, compilerDetailsLogMessage: string ) { const configFilePath = findConfigFile(compiler, path.dirname(loader.resourcePath), loaderOptions.configFileName); - let configFileError: interfaces.WebpackError; + let configFileError: WebpackError | undefined; let configFile: ConfigFile; - if (configFilePath) { + if (configFilePath !== undefined) { if (compilerCompatible) { log.logInfo(green(`${compilerDetailsLogMessage} and ${configFilePath}`)); } else { @@ -32,13 +37,13 @@ export function getConfigFile( // HACK: relies on the fact that passing an extra argument won't break // the old API that has a single parameter - configFile = ( compiler).readConfigFile( + configFile = ( compiler).readConfigFile( configFilePath, compiler.sys.readFile ); - if (configFile.error) { - configFileError = utils.formatErrors([configFile.error], loaderOptions, compiler, { file: configFilePath })[0]; + if (configFile.error !== undefined) { + configFileError = formatErrors([configFile.error], loaderOptions, compiler, { file: configFilePath })[0]; } } else { if (compilerCompatible) { log.logInfo(green(compilerDetailsLogMessage)); } @@ -51,7 +56,7 @@ export function getConfigFile( }; } - if (!configFileError) { + if (configFileError === undefined) { configFile.config.compilerOptions = Object.assign({}, configFile.config.compilerOptions, loaderOptions.compilerOptions); @@ -68,7 +73,7 @@ export function getConfigFile( * The tsconfig.json is found using the same method as `tsc`, starting in the current directory * and continuing up the parent directory chain. */ -function findConfigFile(compiler: typeof typescript, searchPath: string, configFileName: string): string { +function findConfigFile(compiler: typeof typescript, searchPath: string, configFileName: string): string | undefined { while (true) { const fileName = path.join(searchPath, configFileName); if (compiler.sys.fileExists(fileName)) { @@ -91,13 +96,13 @@ export function getConfigParseResult( let configParseResult: typescript.ParsedCommandLine; if (typeof ( compiler).parseJsonConfigFileContent === 'function') { // parseConfigFile was renamed between 1.6.2 and 1.7 - configParseResult = ( compiler).parseJsonConfigFileContent( + configParseResult = (/**/ compiler).parseJsonConfigFileContent( configFile.config, compiler.sys, path.dirname(configFilePath || '') ); } else { - configParseResult = ( compiler).parseConfigFile( + configParseResult = (/**/ compiler).parseConfigFile( configFile.config, compiler.sys, path.dirname(configFilePath || '') diff --git a/src/index.ts b/src/index.ts index 4acddfa94..e3baffa6c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,34 +1,53 @@ -import path = require('path'); -import loaderUtils = require('loader-utils'); - -import instances = require('./instances'); -import interfaces = require('./interfaces'); -import utils = require('./utils'); -import constants = require('./constants'); - -const webpackInstances: interfaces.Compiler[] = []; -const loaderOptionsCache: interfaces.LoaderOptionsCache = {}; - -type PartialLoaderOptions = interfaces.Partial; +import * as path from 'path'; +import * as loaderUtils from 'loader-utils'; + +import { getTypeScriptInstance } from './instances'; +import { appendSuffixesIfMatch, arrify, formatErrors, hasOwnProperty, registerWebpackErrors } from './utils'; +import * as constants from './constants'; +import { + AsyncCallback, + Compiler, + LoaderOptions, + LoaderOptionsCache, + TSFile, + TSInstance, + Webpack +} from './interfaces'; + +const webpackInstances: Compiler[] = []; +const loaderOptionsCache: LoaderOptionsCache = {}; + +type PartialLoaderOptions = Partial; /** * The entry point for ts-loader */ -function loader(this: interfaces.Webpack, contents: string) { +function loader(this: Webpack, contents: string) { this.cacheable && this.cacheable(); const callback = this.async(); const options = getLoaderOptions(this); - const { instance, error } = instances.getTypeScriptInstance(options, this); + const instanceOrError = getTypeScriptInstance(options, this); - if (error) { - callback(error); + if (instanceOrError.error !== undefined) { + callback(instanceOrError.error); return; } - const rawFilePath = path.normalize(this.resourcePath); + return successLoader(this, contents, callback, options, instanceOrError.instance!); +} + +function successLoader( + loader: Webpack, + contents: string, + callback: AsyncCallback, + options: LoaderOptions, + instance: TSInstance +) { + + const rawFilePath = path.normalize(loader.resourcePath); const filePath = options.appendTsSuffixTo.length > 0 || options.appendTsxSuffixTo.length > 0 - ? utils.appendSuffixesIfMatch({ + ? appendSuffixesIfMatch({ '.ts': options.appendTsSuffixTo, '.tsx': options.appendTsxSuffixTo, }, rawFilePath) @@ -37,8 +56,8 @@ function loader(this: interfaces.Webpack, contents: string) { const fileVersion = updateFileInCache(filePath, contents, instance); const { outputText, sourceMapText } = options.transpileOnly - ? getTranspilationEmit(filePath, contents, instance, this) - : getEmit(rawFilePath, filePath, instance, this); + ? getTranspilationEmit(filePath, contents, instance, loader) + : getEmit(rawFilePath, filePath, instance, loader); if (outputText === null || outputText === undefined) { const additionalGuidance = filePath.indexOf('node_modules') !== -1 @@ -47,14 +66,14 @@ function loader(this: interfaces.Webpack, contents: string) { throw new Error(`Typescript emitted no output for ${filePath}.${additionalGuidance}`); } - const { sourceMap, output } = makeSourceMap(sourceMapText, outputText, filePath, contents, this); + const { sourceMap, output } = makeSourceMap(sourceMapText, outputText, filePath, contents, loader); // _module.meta is not available inside happypack if (!options.happyPackMode) { // Make sure webpack is aware that even though the emitted JavaScript may be the same as // a previously cached version the TypeScript may be different and therefore should be // treated as new - this._module.meta.tsLoaderFileVersion = fileVersion; + loader._module.meta.tsLoaderFileVersion = fileVersion; } callback(null, output, sourceMap); @@ -64,19 +83,19 @@ function loader(this: interfaces.Webpack, contents: string) { * either retrieves loader options from the cache * or creates them, adds them to the cache and returns */ -function getLoaderOptions(loader: interfaces.Webpack) { +function getLoaderOptions(loader: Webpack) { // differentiate the TypeScript instance based on the webpack instance let webpackIndex = webpackInstances.indexOf(loader._compiler); if (webpackIndex === -1) { webpackIndex = webpackInstances.push(loader._compiler) - 1; } - const queryOptions = loaderUtils.getOptions(loader) || {} as interfaces.LoaderOptions; + const queryOptions = loaderUtils.getOptions(loader) || {} as LoaderOptions; const configFileOptions: PartialLoaderOptions = loader.options.ts || {}; const instanceName = webpackIndex + '_' + (queryOptions.instance || configFileOptions.instance || 'default'); - if (utils.hasOwnProperty(loaderOptionsCache, instanceName)) { + if (hasOwnProperty(loaderOptionsCache, instanceName)) { return loaderOptionsCache[instanceName]; } @@ -96,7 +115,7 @@ function getLoaderOptions(loader: interfaces.Webpack) { happyPackMode: false, }, configFileOptions, queryOptions); - options.ignoreDiagnostics = utils.arrify(options.ignoreDiagnostics).map(Number); + options.ignoreDiagnostics = arrify(options.ignoreDiagnostics).map(Number); options.logLevel = options.logLevel.toUpperCase(); options.instance = instanceName; @@ -112,17 +131,17 @@ function getLoaderOptions(loader: interfaces.Webpack) { * Either add file to the overall files cache or update it in the cache when the file contents have changed * Also add the file to the modified files */ -function updateFileInCache(filePath: string, contents: string, instance: interfaces.TSInstance) { +function updateFileInCache(filePath: string, contents: string, instance: TSInstance) { // Update file contents let file = instance.files[filePath]; - if (!file) { - file = instance.files[filePath] = { version: 0 }; + if (file === undefined) { + file = instance.files[filePath] = { version: 0 }; } if (file.text !== contents) { file.version++; file.text = contents; - instance.version++; + instance.version!++; } // push this file to modified files hash. @@ -136,24 +155,26 @@ function updateFileInCache(filePath: string, contents: string, instance: interfa function getEmit( rawFilePath: string, filePath: string, - instance: interfaces.TSInstance, - loader: interfaces.Webpack + instance: TSInstance, + loader: Webpack ) { // Emit Javascript - const output = instance.languageService.getEmitOutput(filePath); + const output = instance.languageService!.getEmitOutput(filePath); loader.clearDependencies(); loader.addDependency(rawFilePath); - const allDefinitionFiles = Object.keys(instance.files).filter(defFilePath => !!defFilePath.match(constants.dtsDtsxRegex)); + const allDefinitionFiles = Object.keys(instance.files).filter(defFilePath => defFilePath.match(constants.dtsDtsxRegex)); // Make this file dependent on *all* definition files in the program const addDependency = loader.addDependency.bind(loader); allDefinitionFiles.forEach(addDependency); // Additionally make this file dependent on all imported files - const additionalDependencies = instance.dependencyGraph[filePath] - && instance.dependencyGraph[filePath].map(module => module.originalFileName); + const fileDependencies = instance.dependencyGraph[filePath]; + const additionalDependencies = fileDependencies === undefined + ? [] + : fileDependencies.map(module => module.originalFileName); if (additionalDependencies) { additionalDependencies.forEach(addDependency); } @@ -162,10 +183,10 @@ function getEmit( .concat(additionalDependencies) .map(defFilePath => defFilePath + '@' + (instance.files[defFilePath] || { version: '?' }).version); - const outputFile = output.outputFiles.filter(outputFile => !!outputFile.name.match(constants.jsJsx)).pop(); + const outputFile = output.outputFiles.filter(outputFile => outputFile.name.match(constants.jsJsx)).pop(); const outputText = (outputFile) ? outputFile.text : undefined; - const sourceMapFile = output.outputFiles.filter(outputFile => !!outputFile.name.match(constants.jsJsxMap)).pop(); + const sourceMapFile = output.outputFiles.filter(outputFile => outputFile.name.match(constants.jsJsxMap)).pop(); const sourceMapText = (sourceMapFile) ? sourceMapFile.text : undefined; return { outputText, sourceMapText }; @@ -177,8 +198,8 @@ function getEmit( function getTranspilationEmit( filePath: string, contents: string, - instance: interfaces.TSInstance, - loader: interfaces.Webpack + instance: TSInstance, + loader: Webpack ) { const fileName = path.basename(filePath); @@ -191,9 +212,9 @@ function getTranspilationEmit( // _module.errors is not available inside happypack - see https://github.com/TypeStrong/ts-loader/issues/336 if (!instance.loaderOptions.happyPackMode) { - utils.registerWebpackErrors( + registerWebpackErrors( loader._module.errors, - utils.formatErrors(diagnostics, instance.loaderOptions, instance.compiler, { module: loader._module }) + formatErrors(diagnostics, instance.loaderOptions, instance.compiler, { module: loader._module }) ); } @@ -201,14 +222,14 @@ function getTranspilationEmit( } function makeSourceMap( - sourceMapText: string, + sourceMapText: string | undefined, outputText: string, filePath: string, contents: string, - loader: interfaces.Webpack + loader: Webpack ) { - if (!sourceMapText) { - return { output: outputText, sourceMap: undefined as interfaces.SourceMap }; + if (sourceMapText === undefined) { + return { output: outputText, sourceMap: undefined }; } return { diff --git a/src/instances.ts b/src/instances.ts index be13a9c75..02f017709 100644 --- a/src/instances.ts +++ b/src/instances.ts @@ -1,16 +1,24 @@ -import path = require('path'); -import fs = require('fs'); - -import afterCompile = require('./after-compile'); -import config = require('./config'); -import compilerSetup = require('./compilerSetup'); -import interfaces = require('./interfaces'); -import utils = require('./utils'); -import logger = require('./logger'); -import makeServicesHost = require('./servicesHost'); -import watchRun = require('./watch-run'); - -const instances = {}; +import * as typescript from 'typescript'; +import * as path from 'path'; +import * as fs from 'fs'; + +import { makeAfterCompile } from './after-compile'; +import { getConfigFile, getConfigParseResult } from './config'; +import { getCompilerOptions, getCompiler } from './compilerSetup'; +import { hasOwnProperty, makeError, formatErrors, registerWebpackErrors } from './utils'; +import * as logger from './logger'; +import { makeServicesHost } from './servicesHost'; +import { makeWatchRun } from './watch-run'; +import { + LoaderOptions, + TSFiles, + TSInstance, + TSInstances, + Webpack, + WebpackError +} from './interfaces'; + +const instances = {}; /** * The loader is executed once for each file seen by webpack. However, we need to keep @@ -20,59 +28,75 @@ const instances = {}; * `instance` property. */ export function getTypeScriptInstance( - loaderOptions: interfaces.LoaderOptions, - loader: interfaces.Webpack -): { instance?: interfaces.TSInstance, error?: interfaces.WebpackError } { - if (utils.hasOwnProperty(instances, loaderOptions.instance)) { + loaderOptions: LoaderOptions, + loader: Webpack +): { instance?: TSInstance, error?: WebpackError } { + if (hasOwnProperty(instances, loaderOptions.instance)) { return { instance: instances[loaderOptions.instance] }; } const log = logger.makeLogger(loaderOptions); - const { compiler, compilerCompatible, compilerDetailsLogMessage, errorMessage } = compilerSetup.getCompiler(loaderOptions, log); + const compiler = getCompiler(loaderOptions, log); - if (errorMessage) { - return { error: utils.makeError({ rawMessage: errorMessage }) }; + if (compiler.errorMessage !== undefined) { + return { error: makeError({ rawMessage: compiler.errorMessage }) }; } - const { - configFilePath, - configFile, - configFileError - } = config.getConfigFile(compiler, loader, loaderOptions, compilerCompatible, log, compilerDetailsLogMessage); + return successfulTypeScriptInstance( + loaderOptions, loader, log, + compiler.compiler!, compiler.compilerCompatible!, compiler.compilerDetailsLogMessage! + ); +} - if (configFileError) { - return { error: configFileError }; +function successfulTypeScriptInstance( + loaderOptions: LoaderOptions, + loader: Webpack, + log: logger.Logger, + compiler: typeof typescript, + compilerCompatible: boolean, + compilerDetailsLogMessage: string +) { + const configFileAndPath = getConfigFile(compiler, loader, loaderOptions, compilerCompatible, log, compilerDetailsLogMessage!); + + if (configFileAndPath.configFileError !== undefined) { + return { error: configFileAndPath.configFileError }; } - const configParseResult = config.getConfigParseResult(compiler, configFile, configFilePath); + const { configFilePath } = configFileAndPath; - if (configParseResult.errors.length && !loaderOptions.happyPackMode) { - utils.registerWebpackErrors( + const configParseResult = getConfigParseResult(compiler, configFileAndPath.configFile, configFileAndPath.configFilePath!); + + if (configParseResult.errors.length > 0 && !loaderOptions.happyPackMode) { + registerWebpackErrors( loader._module.errors, - utils.formatErrors(configParseResult.errors, loaderOptions, compiler, { file: configFilePath })); + formatErrors(configParseResult.errors, loaderOptions, compiler, { file: configFilePath })); - return { error: utils.makeError({ rawMessage: 'error while parsing tsconfig.json', file: configFilePath }) }; + return { error: makeError({ rawMessage: 'error while parsing tsconfig.json', file: configFilePath }) }; } - const compilerOptions = compilerSetup.getCompilerOptions(compilerCompatible, compiler, configParseResult); - const files: interfaces.TSFiles = {}; + const compilerOptions = getCompilerOptions(compilerCompatible, compiler!, configParseResult); + const files: TSFiles = {}; const getCustomTransformers = loaderOptions.getCustomTransformers || Function.prototype; if (loaderOptions.transpileOnly) { // quick return for transpiling // we do need to check for any issues with TS options though - const program = compiler.createProgram([], compilerOptions); + const program = compiler!.createProgram([], compilerOptions); const diagnostics = program.getOptionsDiagnostics(); // happypack does not have _module.errors - see https://github.com/TypeStrong/ts-loader/issues/336 if (!loaderOptions.happyPackMode) { - utils.registerWebpackErrors( + registerWebpackErrors( loader._module.errors, - utils.formatErrors(diagnostics, loaderOptions, compiler, {file: configFilePath || 'tsconfig.json'})); + formatErrors(diagnostics, loaderOptions, compiler!, {file: configFilePath || 'tsconfig.json'})); } - return { instance: instances[loaderOptions.instance] = { compiler, compilerOptions, loaderOptions, files, dependencyGraph: {}, reverseDependencyGraph: {}, transformers: getCustomTransformers() }}; + const instance = { compiler, compilerOptions, loaderOptions, files, dependencyGraph: {}, reverseDependencyGraph: {}, transformers: getCustomTransformers() }; + + instances[loaderOptions.instance] = instance; + + return { instance }; } // Load initial files (core lib files, any files specified in tsconfig.json) @@ -87,8 +111,8 @@ export function getTypeScriptInstance( }; }); } catch (exc) { - return { error: utils.makeError({ - rawMessage: `A file specified in tsconfig.json could not be found: ${ normalizedFilePath }` + return { error: makeError({ + rawMessage: `A file specified in tsconfig.json could not be found: ${ normalizedFilePath! }` }) }; } @@ -97,7 +121,7 @@ export function getTypeScriptInstance( ? /\.tsx?$|\.jsx?$/i : /\.tsx?$/i; - const instance: interfaces.TSInstance = instances[loaderOptions.instance] = { + const instance: TSInstance = instances[loaderOptions.instance] = { compiler, compilerOptions, loaderOptions, @@ -113,8 +137,8 @@ export function getTypeScriptInstance( const servicesHost = makeServicesHost(scriptRegex, log, loader, instance, loaderOptions.appendTsSuffixTo, loaderOptions.appendTsxSuffixTo); instance.languageService = compiler.createLanguageService(servicesHost, compiler.createDocumentRegistry()); - loader._compiler.plugin("after-compile", afterCompile(instance, configFilePath)); - loader._compiler.plugin("watch-run", watchRun(instance)); + loader._compiler.plugin("after-compile", makeAfterCompile(instance, configFilePath)); + loader._compiler.plugin("watch-run", makeWatchRun(instance)); return { instance }; } diff --git a/src/interfaces.ts b/src/interfaces.ts index 557b95c51..9ee5a24d8 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -1,4 +1,4 @@ -import typescript = require('typescript'); +import * as typescript from 'typescript'; export interface SourceMap { sources: any[]; @@ -6,6 +6,10 @@ export interface SourceMap { sourcesContent: string[]; } +export interface AsyncCallback { + (err: Error | WebpackError | null, source?: string, map?: string): void; +} + /** * Details here: https://webpack.github.io/docs/loaders.html#loader-context */ @@ -35,7 +39,7 @@ export interface Webpack { * A data object shared between the pitch and the normal phase. */ data: Object; - async: () => (err: Error | WebpackError, source?: string, map?: string) => void; + async: () => AsyncCallback; /** * The resource part of the request, including query. * eg: "/abc/resource.js?rrr" @@ -193,12 +197,12 @@ export interface Resolve { } export interface ResolveSync { - (context: string, path: string, moduleName: string): string; + (context: string | undefined, path: string, moduleName: string): string; } export interface ModuleResolutionHost { fileExists(fileName: string): boolean; - readFile(fileName: string): string + readFile(fileName: string): string; } export interface TSInstance { @@ -212,8 +216,8 @@ export interface TSInstance { /** * contains the modified files - cleared each time after-compile is called */ - modifiedFiles?: TSFiles; - languageService?: typescript.LanguageService; + modifiedFiles?: TSFiles | null; + languageService?: typescript.LanguageService | null; version?: number; dependencyGraph: DependencyGraph; reverseDependencyGraph: ReverseDependencyGraph; @@ -230,13 +234,13 @@ export interface TSInstances { } export interface DependencyGraph { - [file: string]: ResolvedModule[]; + [file: string]: ResolvedModule[] | undefined; } export interface ReverseDependencyGraph { [file: string]: { [file: string]: boolean - }; + } | undefined; } export type Partial = { @@ -267,7 +271,7 @@ export interface TSFile { } export interface TSFiles { - [fileName: string]: TSFile; + [fileName: string]: TSFile | undefined; } export interface ResolvedModule { diff --git a/src/logger.ts b/src/logger.ts index 29b0b0204..457591429 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,5 +1,5 @@ -import interfaces = require('./interfaces'); -var Console = require('console').Console; +const Console = require('console').Console; +import { LoaderOptions } from './interfaces'; const stderrConsole = new Console(process.stderr); const stdoutConsole = new Console(process.stdout); @@ -16,30 +16,30 @@ interface InternalLoggerFunc { const doNothingLogger = (..._messages: string[]) => {}; -function makeLoggerFunc(loaderOptions: interfaces.LoaderOptions) { +function makeLoggerFunc(loaderOptions: LoaderOptions) { return loaderOptions.silent ? (_whereToLog: any, _messages: string[]) => {} : (whereToLog: any, messages: string[]) => console.log.apply(whereToLog, messages); } -function makeExternalLogger(loaderOptions: interfaces.LoaderOptions, logger: InternalLoggerFunc) { +function makeExternalLogger(loaderOptions: LoaderOptions, logger: InternalLoggerFunc) { const output = loaderOptions.logInfoToStdOut ? stdoutConsole : stderrConsole; return (...messages: string[]) => logger(output, messages); } -function makeLogInfo(loaderOptions: interfaces.LoaderOptions, logger: InternalLoggerFunc) { +function makeLogInfo(loaderOptions: LoaderOptions, logger: InternalLoggerFunc) { return LogLevel[loaderOptions.logLevel] <= LogLevel.INFO ? (...messages: string[]) => logger(loaderOptions.logInfoToStdOut ? stdoutConsole : stderrConsole, messages) : doNothingLogger } -function makeLogError(loaderOptions: interfaces.LoaderOptions, logger: InternalLoggerFunc) { +function makeLogError(loaderOptions: LoaderOptions, logger: InternalLoggerFunc) { return LogLevel[loaderOptions.logLevel] <= LogLevel.ERROR ? (...messages: string[]) => logger(stderrConsole, messages) : doNothingLogger } -function makeLogWarning(loaderOptions: interfaces.LoaderOptions, logger: InternalLoggerFunc) { +function makeLogWarning(loaderOptions: LoaderOptions, logger: InternalLoggerFunc) { return LogLevel[loaderOptions.logLevel] <= LogLevel.WARN ? (...messages: string[]) => logger(stderrConsole, messages) : doNothingLogger @@ -56,7 +56,7 @@ export interface Logger { logError: LoggerFunc; } -export function makeLogger(loaderOptions: interfaces.LoaderOptions): Logger { +export function makeLogger(loaderOptions: LoaderOptions): Logger { const logger = makeLoggerFunc(loaderOptions); return { log: makeExternalLogger(loaderOptions, logger), diff --git a/src/resolver.ts b/src/resolver.ts index 242abd2cf..b9ba5d36a 100644 --- a/src/resolver.ts +++ b/src/resolver.ts @@ -1,9 +1,11 @@ -import interfaces = require('./interfaces'); +import { + Resolve, + ResolveSync +} from './interfaces'; const node = require("enhanced-resolve/lib/node"); -function makeResolver(options: { resolve: interfaces.Resolve }): interfaces.ResolveSync { +export function makeResolver(options: { resolve: Resolve }): ResolveSync { return node.create.sync(options.resolve); } -export = makeResolver; diff --git a/src/servicesHost.ts b/src/servicesHost.ts index d3e02f623..f094ba5da 100644 --- a/src/servicesHost.ts +++ b/src/servicesHost.ts @@ -1,19 +1,26 @@ -import typescript = require('typescript'); -import constants = require('./constants'); -import interfaces = require('./interfaces'); -import logger = require('./logger'); -import path = require('path'); -import makeResolver = require('./resolver'); -import utils = require('./utils'); +import * as typescript from 'typescript'; +import * as path from 'path'; + +import * as constants from './constants'; +import * as logger from './logger'; +import { makeResolver } from './resolver'; +import { appendSuffixesIfMatch, readFile } from './utils'; +import { + ModuleResolutionHost, + ResolvedModule, + ResolveSync, + TSInstance, + Webpack +} from './interfaces'; /** * Create the TypeScript language service */ -function makeServicesHost( +export function makeServicesHost( scriptRegex: RegExp, log: logger.Logger, - loader: interfaces.Webpack, - instance: interfaces.TSInstance, + loader: Webpack, + instance: TSInstance, appendTsSuffixTo: RegExp[], appendTsxSuffixTo: RegExp[] ) { @@ -21,23 +28,24 @@ function makeServicesHost( const newLine = compilerOptions.newLine === constants.CarriageReturnLineFeedCode ? constants.CarriageReturnLineFeed : - compilerOptions.newLine === constants.LineFeedCode ? constants.LineFeed : - constants.EOL; + compilerOptions.newLine === constants.LineFeedCode ? constants.LineFeed : + constants.EOL; // make a (sync) resolver that follows webpack's rules const resolveSync = makeResolver(loader.options); - const moduleResolutionHost = { - fileExists: (fileName: string) => utils.readFile(fileName) !== undefined, - readFile: (fileName: string) => utils.readFile(fileName), + const moduleResolutionHost: ModuleResolutionHost = { + fileExists: (fileName: string) => readFile(fileName) !== undefined, + readFile: (fileName: string) => readFile(fileName) || '', }; return { getProjectVersion: () => `${instance.version}`, - getScriptFileNames: () => Object.keys(files).filter(filePath => !!filePath.match(scriptRegex)), + getScriptFileNames: () => Object.keys(files).filter(filePath => filePath.match(scriptRegex)), getScriptVersion: (fileName: string) => { fileName = path.normalize(fileName); - return files[fileName] && files[fileName].version.toString(); + const file = files[fileName]; + return file === undefined ? '' : file.version.toString(); }, getScriptSnapshot: (fileName: string) => { // This is called any time TypeScript needs a file's text @@ -45,9 +53,9 @@ function makeServicesHost( fileName = path.normalize(fileName); let file = files[fileName]; - if (!file) { - const text = utils.readFile(fileName); - if (!text) { return undefined; } + if (file === undefined) { + const text = readFile(fileName); + if (text === undefined) { return undefined; } file = files[fileName] = { version: 0, text }; } @@ -85,12 +93,12 @@ function makeServicesHost( } function resolveModuleNames( - resolveSync: interfaces.ResolveSync, - moduleResolutionHost: interfaces.ModuleResolutionHost, + resolveSync: ResolveSync, + moduleResolutionHost: ModuleResolutionHost, appendTsSuffixTo: RegExp[], appendTsxSuffixTo: RegExp[], scriptRegex: RegExp, - instance: interfaces.TSInstance, + instance: TSInstance, moduleNames: string[], containingFile: string ) { @@ -105,25 +113,25 @@ function resolveModuleNames( } function resolveModuleName( - resolveSync: interfaces.ResolveSync, - moduleResolutionHost: interfaces.ModuleResolutionHost, + resolveSync: ResolveSync, + moduleResolutionHost: ModuleResolutionHost, appendTsSuffixTo: RegExp[], appendTsxSuffixTo: RegExp[], scriptRegex: RegExp, - instance: interfaces.TSInstance, + instance: TSInstance, moduleName: string, containingFile: string ) { const { compiler, compilerOptions } = instance; - let resolutionResult: interfaces.ResolvedModule; + let resolutionResult: ResolvedModule; try { const originalFileName = resolveSync(undefined, path.normalize(path.dirname(containingFile)), moduleName); const resolvedFileName = appendTsSuffixTo.length > 0 || appendTsxSuffixTo.length > 0 - ? utils.appendSuffixesIfMatch({ + ? appendSuffixesIfMatch({ '.ts': appendTsSuffixTo, '.tsx': appendTsxSuffixTo, }, originalFileName) @@ -136,27 +144,27 @@ function resolveModuleName( const tsResolution = compiler.resolveModuleName(moduleName, containingFile, compilerOptions, moduleResolutionHost); - if (tsResolution.resolvedModule) { + if (tsResolution.resolvedModule !== undefined) { const resolvedFileName = path.normalize(tsResolution.resolvedModule.resolvedFileName); - const tsResolutionResult: interfaces.ResolvedModule = { + const tsResolutionResult: ResolvedModule = { originalFileName: resolvedFileName, resolvedFileName, isExternalLibraryImport: tsResolution.resolvedModule.isExternalLibraryImport }; - if (resolutionResult) { - if (resolutionResult.resolvedFileName === tsResolutionResult.resolvedFileName) { - resolutionResult.isExternalLibraryImport = tsResolutionResult.isExternalLibraryImport; + if (resolutionResult!) { + if (resolutionResult!.resolvedFileName === tsResolutionResult.resolvedFileName) { + resolutionResult!.isExternalLibraryImport = tsResolutionResult.isExternalLibraryImport; } } else { resolutionResult = tsResolutionResult; } } - return resolutionResult; + return resolutionResult!; } function populateDependencyGraphs( - resolvedModules: interfaces.ResolvedModule[], - instance: interfaces.TSInstance, + resolvedModules: ResolvedModule[], + instance: TSInstance, containingFile: string ) { resolvedModules = resolvedModules @@ -165,11 +173,9 @@ function populateDependencyGraphs( instance.dependencyGraph[path.normalize(containingFile)] = resolvedModules; resolvedModules.forEach(resolvedModule => { - if (!instance.reverseDependencyGraph[resolvedModule.resolvedFileName]) { + if (instance.reverseDependencyGraph[resolvedModule.resolvedFileName] === undefined) { instance.reverseDependencyGraph[resolvedModule.resolvedFileName] = {}; } - instance.reverseDependencyGraph[resolvedModule.resolvedFileName][path.normalize(containingFile)] = true; + instance.reverseDependencyGraph[resolvedModule.resolvedFileName]![path.normalize(containingFile)] = true; }); } - -export = makeServicesHost; diff --git a/src/tsconfig.json b/src/tsconfig.json index 3a52c0557..b7ef2860c 100644 --- a/src/tsconfig.json +++ b/src/tsconfig.json @@ -7,7 +7,7 @@ "noUnusedLocals": true, "noUnusedParameters": true, "suppressImplicitAnyIndexErrors": true, - "strictNullChecks": false, + "strictNullChecks": true, "lib": [ "es5", "es2015.core" ], diff --git a/src/utils.ts b/src/utils.ts index e4ad77240..b3f164315 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,13 +1,19 @@ -import typescript = require('typescript'); -import path = require('path'); -import fs = require('fs'); +import * as typescript from 'typescript'; +import * as path from 'path'; +import * as fs from 'fs'; import { white, red, cyan } from 'chalk'; import constants = require('./constants'); -import interfaces = require('./interfaces'); - -export function registerWebpackErrors(existingErrors: interfaces.WebpackError[], errorsToPush: interfaces.WebpackError[]) { - Array.prototype.splice.apply(existingErrors, (<(number | interfaces.WebpackError)[]>[0, 0]).concat(errorsToPush)); +import { + DependencyGraph, + LoaderOptions, + ReverseDependencyGraph, + WebpackError, + WebpackModule +} from './interfaces'; + +export function registerWebpackErrors(existingErrors: WebpackError[], errorsToPush: WebpackError[]) { + Array.prototype.splice.apply(existingErrors, (<(number | WebpackError)[]>[0, 0]).concat(errorsToPush)); } export function hasOwnProperty(obj: T, property: string) { @@ -19,36 +25,38 @@ export function hasOwnProperty(obj: T, property: string) { * Optionally adds a file name */ export function formatErrors( - diagnostics: typescript.Diagnostic[], - loaderOptions: interfaces.LoaderOptions, + diagnostics: typescript.Diagnostic[] | undefined, + loaderOptions: LoaderOptions, compiler: typeof typescript, - merge?: { file?: string; module?: interfaces.WebpackModule } -): interfaces.WebpackError[] { + merge?: { file?: string; module?: WebpackModule } +): WebpackError[] { return diagnostics - .filter(diagnostic => loaderOptions.ignoreDiagnostics.indexOf(diagnostic.code) === -1) - .map(diagnostic => { - const errorCategory = compiler.DiagnosticCategory[diagnostic.category].toLowerCase(); - const errorCategoryAndCode = errorCategory + ' TS' + diagnostic.code + ': '; - - const messageText = errorCategoryAndCode + compiler.flattenDiagnosticMessageText(diagnostic.messageText, constants.EOL); - let error: interfaces.WebpackError; - if (diagnostic.file) { - const lineChar = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); - let errorMessage = `${white('(')}${cyan((lineChar.line + 1).toString())},${cyan((lineChar.character + 1).toString())}): ${red(messageText)}`; - if (loaderOptions.visualStudioErrorFormat) { - errorMessage = red(path.normalize(diagnostic.file.fileName)) + errorMessage; + ? diagnostics + .filter(diagnostic => loaderOptions.ignoreDiagnostics.indexOf(diagnostic.code) === -1) + .map(diagnostic => { + const errorCategory = compiler.DiagnosticCategory[diagnostic.category].toLowerCase(); + const errorCategoryAndCode = errorCategory + ' TS' + diagnostic.code + ': '; + + const messageText = errorCategoryAndCode + compiler.flattenDiagnosticMessageText(diagnostic.messageText, constants.EOL); + let error: WebpackError; + if (diagnostic.file !== undefined) { + const lineChar = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start!); + let errorMessage = `${white('(')}${cyan((lineChar.line + 1).toString())},${cyan((lineChar.character + 1).toString())}): ${red(messageText)}`; + if (loaderOptions.visualStudioErrorFormat) { + errorMessage = red(path.normalize(diagnostic.file.fileName)) + errorMessage; + } + error = makeError({ + message: errorMessage, + rawMessage: messageText, + location: { line: lineChar.line + 1, character: lineChar.character + 1 } + }); + } else { + error = makeError({ rawMessage: messageText }); } - error = makeError({ - message: errorMessage, - rawMessage: messageText, - location: { line: lineChar.line + 1, character: lineChar.character + 1 } - }); - } else { - error = makeError({ rawMessage: messageText }); - } - return Object.assign(error, merge); - }); + return Object.assign(error, merge); + }) + : []; } export function readFile(fileName: string) { @@ -67,14 +75,14 @@ interface MakeError { file?: string; } -export function makeError({ rawMessage, message, location, file }: MakeError): interfaces.WebpackError { +export function makeError({ rawMessage, message, location, file }: MakeError): WebpackError { const error = { rawMessage, message: message || `${red(rawMessage)}`, loaderSource: 'ts-loader' }; - return Object.assign(error, { location, file }); + return Object.assign(error, { location, file }); } export function appendSuffixIfMatch(patterns: RegExp[], path: string, suffix: string): string { @@ -99,9 +107,9 @@ export function appendSuffixesIfMatch(suffixDict: {[suffix: string]: RegExp[]}, * Recursively collect all possible dependants of passed file */ export function collectAllDependants( - reverseDependencyGraph: interfaces.ReverseDependencyGraph, + reverseDependencyGraph: ReverseDependencyGraph, fileName: string, - collected: {[file:string]: boolean} = {} + collected: { [file: string]: boolean } = {} ): string[] { const result = {}; result[fileName] = true; @@ -121,15 +129,15 @@ export function collectAllDependants( * Recursively collect all possible dependencies of passed file */ export function collectAllDependencies( - dependencyGraph: interfaces.DependencyGraph, + dependencyGraph: DependencyGraph, filePath: string, - collected: {[file:string]: boolean} = {} + collected: { [file: string]: boolean } = {} ): string[] { const result = {}; result[filePath] = true; collected[filePath] = true; - let directDependencies = dependencyGraph[filePath]; - if (directDependencies) { + let directDependencies = dependencyGraph[filePath]; + if (directDependencies !== undefined) { directDependencies.forEach(dependencyModule => { if (!collected[dependencyModule.originalFileName]) { collectAllDependencies(dependencyGraph, dependencyModule.resolvedFileName, collected) @@ -141,9 +149,9 @@ export function collectAllDependencies( } export function arrify(val: T | T[]) { - if (val === null || val === undefined) { - return []; - } + if (val === null || val === undefined) { + return []; + } - return Array.isArray(val) ? val : [val]; + return Array.isArray(val) ? val : [val]; }; \ No newline at end of file diff --git a/src/watch-run.ts b/src/watch-run.ts index d7f47cdfc..90ec1917a 100644 --- a/src/watch-run.ts +++ b/src/watch-run.ts @@ -1,17 +1,21 @@ -import path = require('path'); -import utils = require('./utils'); -import interfaces = require('./interfaces'); -import constants = require('./constants'); +import * as path from 'path'; + +import { readFile } from './utils'; +import * as constants from './constants'; +import { + TSInstance, + WebpackWatching +} from './interfaces'; /** * Make function which will manually update changed files */ -function makeWatchRun( - instance: interfaces.TSInstance +export function makeWatchRun( + instance: TSInstance ) { const lastTimes = {}; - let startTime : number = null; - return (watching: interfaces.WebpackWatching, cb: () => void) => { + let startTime : number | null = null; + return (watching: WebpackWatching, cb: () => void) => { if (null === instance.modifiedFiles) { instance.modifiedFiles = {}; } @@ -20,21 +24,19 @@ function makeWatchRun( Object.keys(times) .filter(filePath => times[filePath] > (lastTimes[filePath] || startTime) - && !!filePath.match(constants.tsTsxJsJsxRegex) + && filePath.match(constants.tsTsxJsJsxRegex) ) .forEach(filePath => { lastTimes[filePath] = times[filePath]; filePath = path.normalize(filePath); const file = instance.files[filePath]; - if (file) { - file.text = utils.readFile(filePath) || ''; + if (file !== undefined) { + file.text = readFile(filePath) || ''; file.version++; - instance.version++; - instance.modifiedFiles[filePath] = file; + instance.version!++; + instance.modifiedFiles![filePath] = file; } }); cb(); }; } - -export = makeWatchRun; diff --git a/test/comparison-tests/create-and-execute-test.js b/test/comparison-tests/create-and-execute-test.js index 05e577df6..9a5aa91e1 100644 --- a/test/comparison-tests/create-and-execute-test.js +++ b/test/comparison-tests/create-and-execute-test.js @@ -12,9 +12,6 @@ var glob = require('glob'); var pathExists = require('../pathExists'); var aliasLoader = require('../aliasLoader'); -// force colors on for tests since expected output has colors -require('colors').enabled = true; - var saveOutputMode = process.argv.indexOf('--save-output') !== -1; var indexOfTestToRun = process.argv.indexOf('--test-to-run'); @@ -389,9 +386,9 @@ function normaliseString(platformSpecificContent) { return platformSpecificContent .replace(/\r\n/g, '\n') // replace C:/source/ts-loader/index.js or /home/travis/build/TypeStrong/ts-loader/index.js with ts-loader - .replace(/ \S+[\/|\\]ts-loader[\/|\\]index.js/, 'ts-loader') + .replace(/ \S+[\/|\\]ts-loader[\/|\\]index.js/g, 'ts-loader') // replace (C:/source/ts-loader/dist/index.js with (ts-loader) - .replace(/\(\S+[\/|\\]ts-loader[\/|\\]dist[\/|\\]index.js:\d*:\d*\)/, '(ts-loader)') + .replace(/\(\S+[\/|\\]ts-loader[\/|\\]dist[\/|\\]index.js:\d*:\d*\)/g, '(ts-loader)') // Convert '/' to '\' and back to '/' so slashes are treated the same // whether running / generated on windows or *nix .replace(new RegExp(regexEscape('/'), 'g'), '\\') diff --git a/test/comparison-tests/nodeModulesMeaningfulErrorWhenImportingTs/expectedOutput-2.4/bundle.js b/test/comparison-tests/nodeModulesMeaningfulErrorWhenImportingTs/expectedOutput-2.4/bundle.js index e9179fd3e..90d6b13e2 100644 --- a/test/comparison-tests/nodeModulesMeaningfulErrorWhenImportingTs/expectedOutput-2.4/bundle.js +++ b/test/comparison-tests/nodeModulesMeaningfulErrorWhenImportingTs/expectedOutput-2.4/bundle.js @@ -70,7 +70,7 @@ /* 0 */ /***/ (function(module, exports) { -throw new Error("Module build failed: Error: Typescript emitted no output for /nodeModulesMeaningfulErrorWhenImportingTs/node_modules/a/index.ts.\nYou should not need to recompile .ts files in node_modules.\nPlease contact the package author to advise them to use --declaration --outDir.\nMore https://github.com/Microsoft/TypeScript/issues/12358\n at Object.loader (C:/source/ts-loader/dist/index.js:31:15)"); +throw new Error("Module build failed: Error: Typescript emitted no output for /nodeModulesMeaningfulErrorWhenImportingTs/node_modules/a/index.ts.\nYou should not need to recompile .ts files in node_modules.\nPlease contact the package author to advise them to use --declaration --outDir.\nMore https://github.com/Microsoft/TypeScript/issues/12358\n at successLoader (C:/source/ts-loader/dist/index.js:39:15)\n at Object.loader (C:/source/ts-loader/dist/index.js:21:12)"); /***/ }), /* 1 */ diff --git a/test/comparison-tests/nodeModulesMeaningfulErrorWhenImportingTs/expectedOutput-2.4/output.txt b/test/comparison-tests/nodeModulesMeaningfulErrorWhenImportingTs/expectedOutput-2.4/output.txt index ed8b4e1a4..6b29130d4 100644 --- a/test/comparison-tests/nodeModulesMeaningfulErrorWhenImportingTs/expectedOutput-2.4/output.txt +++ b/test/comparison-tests/nodeModulesMeaningfulErrorWhenImportingTs/expectedOutput-2.4/output.txt @@ -1,7 +1,7 @@ Asset Size Chunks Chunk Names -bundle.js 3.25 kB 0 [emitted] main -chunk {0} bundle.js (main) 528 bytes [entry] [rendered] - [0] ./.test/nodeModulesMeaningfulErrorWhenImportingTs/~/a/index.ts 449 bytes {0} [built] [failed] [1 error] +bundle.js 3.32 kB 0 [emitted] main +chunk {0} bundle.js (main) 596 bytes [entry] [rendered] + [0] ./.test/nodeModulesMeaningfulErrorWhenImportingTs/~/a/index.ts 517 bytes {0} [built] [failed] [1 error] [1] ./.test/nodeModulesMeaningfulErrorWhenImportingTs/app.ts 79 bytes {0} [built] ERROR in ./.test/nodeModulesMeaningfulErrorWhenImportingTs/~/a/index.ts @@ -9,5 +9,6 @@ Module build failed: Error: Typescript emitted no output for node_modules\a\inde You should not need to recompile .ts files in node_modules. Please contact the package author to advise them to use --declaration --outDir. More https://github.com/Microsoft/TypeScript/issues/12358 - at Object.loader (dist\index.js:32:15) + at successLoader (dist\index.js:39:15) + at Object.loader (dist\index.js:21:12) @ ./.test/nodeModulesMeaningfulErrorWhenImportingTs/app.ts 3:8-20 \ No newline at end of file