Issue 1:
Problem

Current module resolution logic is roughly based on Node module loading logic however not all aspects of Node specific module loading were implemented. Also this approach does not really play well with scenarios like RequireJS\ES6 style module loading where resolution of relative files names is performed deterministically using the base url without needing the folder walk. Also current process does not allow user to specify extra locations for module resolution.

Proposal

Instead of using one hybrid way to resolve modules, have two implementations, one for out-of-browser workflows (i.e Node) and one for in-browser versions (ES6). These implementations should closely mimic its runtime counterparts to avoid runtime failures when design time module resolution succeeded and vice versa.

Node Resolution Algorithm

Resolution logic should use the following algorithm (originally taken from Modules all toghether):

require(X) from module at path Y

If exists ambient external module named X {
return the ambient external module
}
else if X begins with './' or '../' or it is rooted path {
try LOAD_AS_FILE(Y + X, loadOnlyDts=false)
try LOAD_AS_DIRECTORY(Y + X, loadOnlyDts=false)
}
else {
LOAD_NODE_MODULES(X, dirname(Y))
}
THROW "not found"

function LOAD_AS_FILE(X, loadOnlyDts) {
if loadOnlyDts then load X.d.ts
else {
if X.ts is a file, load X.ts
else if X.tsx is a file, load X.tsx
else If X.d.ts is a file, load X.d.ts
}
}

function LOAD_AS_DIRECTORY(X, loadOnlyDts) {
If X/package.json is a file {
Parse X/package.json, and look for "typings" field.
if parsed json has field "typings":
let M = X + (json "typings" field)
LOAD_AS_FILE(M, loadOnlyDts).
}
LOAD_AS_FILE(X/index, loadOnlyDts)
}

function LOAD_NODE_MODULES(X, START) {
let DIRS=NODE_MODULES_PATHS(START)
for each DIR in DIRS {
LOAD_AS_FILE(DIR/X, loadOnlyDts=true)
LOAD_AS_DIRECTORY(DIR/X, loadOnlyDts=true)
}
}

function NODE_MODULES_PATHS(START) {
let PARTS = path split(START)
let I = count of PARTS - 1
let DIRS = []
while I >= 0 {
if PARTS[I] = "node_modules" CONTINUE
DIR = path join(PARTS[0 .. I] + "node_modules")
DIRS = DIRS + DIR
let I = I - 1
}
return DIRS
}

RequireJS/ES6 module loader

If module name starts with './' - then name is relative to the file that imports module or calls require.

If module name is a relative path (i.e. 'a/b/c') - it is resolved using the base folder.

Base folder can be either specified explicitly via command line option or can be inferred:

if compiler can uses 'tsconfig.json' to determine files and compilation options then location of 'tsconfig.json' is the base folder

otherwise base folder is common subpath for all explicitly provided files

Path mappings can be used to customize module resolution process. In 'package.json' these mappings can be represented as JSON object with a following structure:

{
"*.ts":"project/ts/*.ts",
"annotations": "/common/core/annotations"
}

Property name represents a pattern that might contain zero or one asterisk (which acts as a capture group). Property value represents a substitution that might contain zero or one asterisk - here it marks the location where captured content will be spliced. For example mapping above for a path 'assert.ts' will produce a string 'project/ts/assert.ts'. Effectively this logic is the same with the implementation of locate function in System.js.

With path mappings in mind module resolution can be described as:

for (var path in [relative_path, relative_path + '.ts', relative_path + "d.ts"]) {
var mappedPath = apply_path_mapping(path);
var candidatePath = isPathRooted(mappedPath) ? mappedPath : combine(baseFolder, mappedPath);
if (fileExists(candidatePath)) {
return candidatePath
}
}
return undefined

With path mappings it becomes trivial to resolve some module names to files located on network share or some location on the disk outside the project folder.

{
"*.ts": "project/scripts/*.ts",
"shared/*": "q:/shared/*.ts"
}

Using this mapping relative path 'shared/core' will be mapped to absolute path 'q:/shared/core.ts'.

We can apply the same resolution rules for both modules and tripleslash references though for the latter onces its is not strictly necessary since they do not implact runtime in any way.

Issue 2:
TypeScript Version:

1.8.7 / nightly (1.9.0-dev.20160217)

Code

Consider the following three files:

a.ts

export class A { }

foo.ts

import {A} from './a';

declare module './a' {
interface A {
foo(): number;
}
}

A.prototype.foo = () => 42;

main.ts

import {A} from './a';
// import './foo';

var a = new A();
console.log(a.foo()); // Shouldn't the second import be necessary in order for this code to compile?

Expected behavior:

The foo member should not be suggested in autocomplete, and main.ts should not compile. That should only happen if foo is imported (i.e. the second import is uncommented).

Actual behavior:

The code compiles, but will fail at runtime with:

TypeError: a.foo is not a function

Additional info:

I think this was working correctly as of early Feb, so this might be a regression introduced by something else.