Skip to content

Commit

Permalink
strict null fork (#589)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
johnnyreilly authored Jul 23, 2017
1 parent 1c1056f commit c3cea1c
Show file tree
Hide file tree
Showing 16 changed files with 369 additions and 294 deletions.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

95 changes: 50 additions & 45 deletions src/after-compile.ts
Original file line number Diff line number Diff line change
@@ -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();
Expand All @@ -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;
Expand All @@ -40,24 +47,24 @@ function makeAfterCompile(
}

interface Modules {
[modulePath: string]: interfaces.WebpackModule[];
[modulePath: string]: WebpackModule[];
}

/**
* handle compiler option errors after the first compile
*/
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' }));
}
Expand All @@ -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);
Expand All @@ -90,26 +97,26 @@ 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];
});
});
}

// re-check files with errors from previous build
if (filesWithErrors) {
if (filesWithErrors !== undefined) {
Object.keys(filesWithErrors).forEach(fileWithErrorName =>
filesToCheckForErrors[fileWithErrorName] = filesWithErrors[fileWithErrorName]
);
Expand All @@ -118,40 +125,40 @@ 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 => {
// remove any existing errors
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 }));
}
});
}
Expand All @@ -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,
Expand All @@ -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) {
Expand All @@ -196,5 +203,3 @@ function removeTSLoaderErrors(errors: interfaces.WebpackError[]) {
}
}
}

export = makeAfterCompile;
24 changes: 12 additions & 12 deletions src/compilerSetup.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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 {
Expand All @@ -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
Expand Down
37 changes: 21 additions & 16 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -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 {
Expand All @@ -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 {
Expand All @@ -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 = (<interfaces.TSCompatibleCompiler> <any> compiler).readConfigFile(
configFile = (<TSCompatibleCompiler> <any> 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)); }
Expand All @@ -51,7 +56,7 @@ export function getConfigFile(
};
}

if (!configFileError) {
if (configFileError === undefined) {
configFile.config.compilerOptions = Object.assign({},
configFile.config.compilerOptions,
loaderOptions.compilerOptions);
Expand All @@ -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)) {
Expand All @@ -91,13 +96,13 @@ export function getConfigParseResult(
let configParseResult: typescript.ParsedCommandLine;
if (typeof (<any> compiler).parseJsonConfigFileContent === 'function') {
// parseConfigFile was renamed between 1.6.2 and 1.7
configParseResult = (<interfaces.TSCompatibleCompiler> <any> compiler).parseJsonConfigFileContent(
configParseResult = (/*<TSCompatibleCompiler>*/ <any> compiler).parseJsonConfigFileContent(
configFile.config,
compiler.sys,
path.dirname(configFilePath || '')
);
} else {
configParseResult = (<interfaces.TSCompatibleCompiler> <any> compiler).parseConfigFile(
configParseResult = (/*<TSCompatibleCompiler>*/ <any> compiler).parseConfigFile(
configFile.config,
compiler.sys,
path.dirname(configFilePath || '')
Expand Down
Loading

0 comments on commit c3cea1c

Please sign in to comment.