Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Throw error if a file not in the TS project is found #945

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 1 addition & 6 deletions src/config.ts
Expand Up @@ -4,15 +4,10 @@ import * as semver from 'semver';
import * as typescript from 'typescript';
import * as webpack from 'webpack';

import { LoaderOptions, WebpackError } from './interfaces';
import { ConfigFile, LoaderOptions, WebpackError } from './interfaces';
import * as logger from './logger';
import { formatErrors } from './utils';

interface ConfigFile {
config?: any;
error?: typescript.Diagnostic;
}

export function getConfigFile(
compiler: typeof typescript,
colors: Chalk,
Expand Down
89 changes: 86 additions & 3 deletions src/index.ts
Expand Up @@ -3,8 +3,13 @@ import * as path from 'path';
import * as typescript from 'typescript';
import * as webpack from 'webpack';

import { getConfigParseResult } from './config';
import * as constants from './constants';
import { getEmitOutput, getTypeScriptInstance } from './instances';
import {
getEmitOutput,
getTypeScriptInstance,
updateInstanceRootFileNames
} from './instances';
import {
LoaderOptions,
LoaderOptionsCache,
Expand Down Expand Up @@ -68,7 +73,24 @@ function successLoader(
)
: rawFilePath;

const fileVersion = updateFileInCache(filePath, contents, instance);
const { version: fileVersion, changedFilesList } = updateFileInCache(
filePath,
contents,
instance
);

if (changedFilesList && !instance.rootFileNames.has(filePath)) {
reloadRootFileNamesFromConfig(loaderContext, instance);
if (
!instance.rootFileNames.has(filePath) &&
!options.onlyCompileBundledFiles
) {
throw new Error(
`The file ${filePath} is not part of the project specified in tsconfig.json. Make sure it is covered by the fields 'include', 'exclude' and 'files'.`
);
}
}

const referencedProject = getAndCacheProjectReference(filePath, instance);
if (referencedProject !== undefined) {
const [relativeProjectConfigPath, relativeFilePath] = [
Expand Down Expand Up @@ -150,6 +172,50 @@ function successLoader(
}
}

/**
* Update the rootFileNames of the instance, by finding all the files
* that match the specs of the tsconfig.
*/
function reloadRootFileNamesFromConfig(
loaderContext: webpack.loader.LoaderContext,
instance: TSInstance
) {
const {
compiler,
basePath,
configFile,
configFilePath,
colors,
loaderOptions
} = instance;

const configParseResult = getConfigParseResult(
compiler,
configFile,
basePath,
configFilePath
);

if (configParseResult.errors.length > 0 && !loaderOptions.happyPackMode) {
const errors = formatErrors(
configParseResult.errors,
loaderOptions,
colors,
compiler,
{ file: configFilePath },
loaderContext.context
);

loaderContext._module.errors.push(...errors);

throw new Error(colors.red('error while parsing tsconfig.json'));
}

updateInstanceRootFileNames(loaderOptions, instance, configParseResult);

return;
}

function makeSourceMapAndFinish(
sourceMapText: string | undefined,
outputText: string | undefined,
Expand Down Expand Up @@ -337,6 +403,8 @@ function updateFileInCache(
instance: TSInstance
) {
let fileWatcherEventKind: typescript.FileWatcherEventKind | undefined;
let changedFilesList = false;

// Update file contents
let file = instance.files.get(filePath);
if (file === undefined) {
Expand All @@ -351,9 +419,21 @@ function updateFileInCache(
file = { version: 0 };
instance.files.set(filePath, file);
}
changedFilesList = true;
instance.changedFilesList = true;
}

// instance.version must be increased when a new root file is added.
//
// Note that the file could actually be in the cache if it was found
// as a dependency before.
//
// See https://github.com/TypeStrong/ts-loader/issues/943
//
if (!instance.rootFileNames.has(filePath)) {
instance.version!++;
}

if (instance.watchHost !== undefined && contents === undefined) {
fileWatcherEventKind = instance.compiler.FileWatcherEventKind.Deleted;
}
Expand Down Expand Up @@ -381,7 +461,10 @@ function updateFileInCache(
instance.modifiedFiles = new Map<string, TSFile>();
}
instance.modifiedFiles.set(filePath, file);
return file.version;
return {
version: file.version,
changedFilesList
};
}

function getEmit(
Expand Down
71 changes: 45 additions & 26 deletions src/instances.ts
Expand Up @@ -199,6 +199,10 @@ function successfulTypeScriptInstance(
compilerOptions,
appendTsTsxSuffixesIfRequired,
loaderOptions,
basePath,
configFile,
configFilePath,
rootFileNames: new Set(),
files,
otherFiles,
program,
Expand All @@ -211,32 +215,6 @@ function successfulTypeScriptInstance(
return { instance: instances[loaderOptions.instance] };
}

// Load initial files (core lib files, any files specified in tsconfig.json)
let normalizedFilePath: string;
try {
const filesToLoad = loaderOptions.onlyCompileBundledFiles
? configParseResult.fileNames.filter(fileName =>
dtsDtsxOrDtsDtsxMapRegex.test(fileName)
)
: configParseResult.fileNames;
filesToLoad.forEach(filePath => {
normalizedFilePath = path.normalize(filePath);
files.set(normalizedFilePath, {
text: fs.readFileSync(normalizedFilePath, 'utf-8'),
version: 0
});
});
} catch (exc) {
return {
error: makeError(
colors.red(
`A file specified in tsconfig.json could not be found: ${normalizedFilePath!}`
),
normalizedFilePath!
)
};
}

// if allowJs is set then we should accept js(x) files
const scriptRegex =
configParseResult.options.allowJs === true
Expand All @@ -248,6 +226,10 @@ function successfulTypeScriptInstance(
compilerOptions,
appendTsTsxSuffixesIfRequired,
loaderOptions,
basePath,
configFile,
configFilePath,
rootFileNames: new Set(),
files,
otherFiles,
languageService: null,
Expand All @@ -265,6 +247,28 @@ function successfulTypeScriptInstance(
);
}

// Load initial files (core lib files, any files specified in tsconfig.json)
updateInstanceRootFileNames(loaderOptions, instance, configParseResult);
let normalizedFilePath: string;
try {
instance.rootFileNames.forEach(filePath => {
normalizedFilePath = path.normalize(filePath);
files.set(normalizedFilePath, {
text: fs.readFileSync(normalizedFilePath, 'utf-8'),
version: 0
});
});
} catch (exc) {
return {
error: makeError(
colors.red(
`A file specified in tsconfig.json could not be found: ${normalizedFilePath!}`
),
normalizedFilePath!
)
};
}

if (loaderOptions.experimentalWatchApi && compiler.createWatchProgram) {
log.logInfo('Using watch api');

Expand Down Expand Up @@ -317,6 +321,21 @@ function successfulTypeScriptInstance(
return { instance };
}

/** Update the set of root filenames in the instance from a parsed tsconfig.json */
export function updateInstanceRootFileNames(
loaderOptions: LoaderOptions,
instance: TSInstance,
configParseResult: typescript.ParsedCommandLine
) {
const files = loaderOptions.onlyCompileBundledFiles
? configParseResult.fileNames.filter(fileName =>
johnnyreilly marked this conversation as resolved.
Show resolved Hide resolved
dtsDtsxOrDtsDtsxMapRegex.test(fileName)
)
: configParseResult.fileNames;

instance.rootFileNames = new Set(files);
}

export function getEmitOutput(instance: TSInstance, filePath: string) {
const program = ensureProgram(instance);
if (program !== undefined) {
Expand Down
13 changes: 13 additions & 0 deletions src/interfaces.ts
Expand Up @@ -3,6 +3,11 @@ import * as typescript from 'typescript';

import { Chalk } from 'chalk';

export interface ConfigFile {
config?: any;
error?: typescript.Diagnostic;
}

export interface ErrorInfo {
code: number;
severity: Severity;
Expand Down Expand Up @@ -55,6 +60,14 @@ export interface TSInstance {
/** Used for Vue for the most part */
appendTsTsxSuffixesIfRequired: (filePath: string) => string;
loaderOptions: LoaderOptions;

basePath: string;
configFile: ConfigFile;
configFilePath: string | undefined;
/**
* Root files as specified by tsconfig.json' include/exclude/files
*/
rootFileNames: Set<string>;
/**
* a cache of all the files
*/
Expand Down
5 changes: 4 additions & 1 deletion test/comparison-tests/appendSuffixTo/tsconfig.json
@@ -1,4 +1,7 @@
{
"compilerOptions": {
}
},
"files": [
"index.vue.ts"
]
}
6 changes: 4 additions & 2 deletions test/comparison-tests/basic/tsconfig.json
Expand Up @@ -3,6 +3,8 @@

},
"files": [
"lib/externalLib.d.ts"
"lib/externalLib.d.ts",
"submodule/submodule.ts",
"app.ts",
]
}
}
7 changes: 2 additions & 5 deletions test/comparison-tests/codeSplitting/tsconfig.json
@@ -1,8 +1,5 @@
{
"compilerOptions": {

},
"files": [
"require.d.ts"
]
}
}
}
8 changes: 2 additions & 6 deletions test/comparison-tests/conditionalRequire/tsconfig.json
@@ -1,9 +1,5 @@
{
"compilerOptions": {

},
"files": [
"require.d.ts",
"globals.d.ts"
]
}
}
}
4 changes: 1 addition & 3 deletions test/comparison-tests/declarationDeps/tsconfig.json
@@ -1,5 +1,3 @@
{
"files": [
"./references.d.ts"
]

}
6 changes: 4 additions & 2 deletions test/comparison-tests/declarationWatch/tsconfig.json
Expand Up @@ -3,6 +3,8 @@

},
"files": [
"thing.d.ts"
"thing.d.ts",
"app.ts",
"dep.ts"
]
}
}
7 changes: 2 additions & 5 deletions test/comparison-tests/es6codeSplitting/tsconfig.json
Expand Up @@ -2,8 +2,5 @@
"compilerOptions": {
"target": "es5",
"module": "commonjs"
},
"files": [
"require.d.ts"
]
}
}
}
5 changes: 1 addition & 4 deletions test/comparison-tests/externals/tsconfig.json
@@ -1,5 +1,2 @@
{
"files": [
"hello.d.ts"
]
}
}
Expand Up @@ -3,6 +3,7 @@

},
"include": [
"app.ts"
"app.ts",
"fake.ts"
]
}
}
@@ -1,5 +1,8 @@
{
"compilerOptions": {

"include": [
"node_modules",
"app.ts"
]
}
}
}