Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
9d298a1
[WIP] enable updating ATA files
uniqueiniquity Nov 21, 2017
ee5e8e3
Ensure proper JSON writing behavior of timestamps
Nov 28, 2017
b0321dc
Refactor to avoid errors
uniqueiniquity Dec 29, 2017
bf4ec1d
Fix timestamp writing, npm install, and cache behavior
uniqueiniquity Jan 3, 2018
0b47a2d
Add tests
uniqueiniquity Jan 4, 2018
4c32ac0
Add test for timestamps write
uniqueiniquity Jan 4, 2018
3f23d5d
Respond to CR
uniqueiniquity Jan 4, 2018
7b6be11
Allow for local timestamp files and style fixes
uniqueiniquity Jan 4, 2018
6a16cfe
Use existing map to hold representations of timestamp files
uniqueiniquity Jan 5, 2018
2332434
Update representation of timestamp file to prevent some extra install…
uniqueiniquity Jan 5, 2018
e72ea6f
Update installed types if older than those listed in the registry
uniqueiniquity Jan 11, 2018
a21f73f
Remove timestamp checking and move registry check into jstyping
uniqueiniquity Jan 11, 2018
87c5945
Revert unnecessary harness changes
uniqueiniquity Jan 11, 2018
2a0d5d1
Fix tests
uniqueiniquity Jan 11, 2018
aff02e8
Move createTypesRegistry so more accessible
uniqueiniquity Jan 11, 2018
d34b865
Respond to CR
uniqueiniquity Jan 19, 2018
7397fb1
Fix lint and test errors and add tests
uniqueiniquity Jan 19, 2018
f8eac24
Make regexes instantiate only once
uniqueiniquity Jan 20, 2018
1d5e5e6
Handle missing ts versions in registry
uniqueiniquity Jan 24, 2018
4c89a81
Handle case where package.json and package-lock.json don't agree
uniqueiniquity Feb 9, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 0 additions & 10 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3999,16 +3999,6 @@ namespace ts {
[option: string]: string[] | boolean | undefined;
}

export interface DiscoverTypingsInfo {
fileNames: string[]; // The file names that belong to the same project.
projectRootPath: string; // The path to the project root directory
safeListPath: string; // The path used to retrieve the safe list
packageNameToTypingLocation: Map<string>; // The map of package names to their cached typing locations
typeAcquisition: TypeAcquisition; // Used to customize the type acquisition process
compilerOptions: CompilerOptions; // Used as a source for typing inference
unresolvedImports: ReadonlyArray<string>; // List of unresolved module ids from imports
}

export enum ModuleKind {
None = 0,
CommonJS = 1,
Expand Down
33 changes: 29 additions & 4 deletions src/harness/unittests/tsserverProjectSystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ namespace ts.projectSystem {
readonly globalTypingsCacheLocation: string,
throttleLimit: number,
installTypingHost: server.ServerHost,
readonly typesRegistry = createMap<void>(),
readonly typesRegistry = createMap<MapLike<string>>(),
log?: TI.Log) {
super(installTypingHost, globalTypingsCacheLocation, safeList.path, customTypesMap.path, throttleLimit, log);
}
Expand Down Expand Up @@ -126,6 +126,25 @@ namespace ts.projectSystem {
return JSON.stringify({ dependencies });
}

export function createTypesRegistry(...list: string[]): Map<MapLike<string>> {
const versionMap = {
"latest": "1.3.0",
"ts2.0": "1.0.0",
"ts2.1": "1.0.0",
"ts2.2": "1.2.0",
"ts2.3": "1.3.0",
"ts2.4": "1.3.0",
"ts2.5": "1.3.0",
"ts2.6": "1.3.0",
"ts2.7": "1.3.0"
};
const map = createMap<MapLike<string>>();
for (const l of list) {
map.set(l, versionMap);
}
return map;
}

export function toExternalFile(fileName: string): protocol.ExternalFile {
return { fileName };
}
Expand Down Expand Up @@ -6528,12 +6547,18 @@ namespace ts.projectSystem {
},
})
};
const typingsCachePackageLockJson: FileOrFolder = {
path: `${typingsCache}/package-lock.json`,
content: JSON.stringify({
dependencies: {
},
})
};

const files = [file, packageJsonInCurrentDirectory, packageJsonOfPkgcurrentdirectory, indexOfPkgcurrentdirectory, typingsCachePackageJson];
const files = [file, packageJsonInCurrentDirectory, packageJsonOfPkgcurrentdirectory, indexOfPkgcurrentdirectory, typingsCachePackageJson, typingsCachePackageLockJson];
const host = createServerHost(files, { currentDirectory });

const typesRegistry = createMap<void>();
typesRegistry.set("pkgcurrentdirectory", void 0);
const typesRegistry = createTypesRegistry("pkgcurrentdirectory");
const typingsInstaller = new TestTypingsInstaller(typingsCache, /*throttleLimit*/ 5, host, typesRegistry);

const projectService = createProjectService(host, { typingsInstaller });
Expand Down
264 changes: 246 additions & 18 deletions src/harness/unittests/typingsInstaller.ts

Large diffs are not rendered by default.

8 changes: 5 additions & 3 deletions src/harness/virtualFileSystemWithWatch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -383,9 +383,11 @@ interface Array<T> {}`
ensureFileOrFolder(fileOrDirectory: FileOrFolder, ignoreWatchInvokedWithTriggerAsFileCreate?: boolean) {
if (isString(fileOrDirectory.content)) {
const file = this.toFile(fileOrDirectory);
Debug.assert(!this.fs.get(file.path));
const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath));
this.addFileOrFolderInFolder(baseFolder, file, ignoreWatchInvokedWithTriggerAsFileCreate);
// file may already exist when updating existing type declaration file
if (!this.fs.get(file.path)) {
const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath));
this.addFileOrFolderInFolder(baseFolder, file, ignoreWatchInvokedWithTriggerAsFileCreate);
}
}
else {
const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory);
Expand Down
2 changes: 1 addition & 1 deletion src/server/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -252,7 +252,7 @@ namespace ts.server {
private requestMap = createMap<QueuedOperation>(); // Maps operation ID to newest requestQueue entry with that ID
/** We will lazily request the types registry on the first call to `isKnownTypesPackageName` and store it in `typesRegistryCache`. */
private requestedRegistry: boolean;
private typesRegistryCache: Map<void> | undefined;
private typesRegistryCache: Map<MapLike<string>> | undefined;

// This number is essentially arbitrary. Processing more than one typings request
// at a time makes sense, but having too many in the pipe results in a hang
Expand Down
2 changes: 1 addition & 1 deletion src/server/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ declare namespace ts.server {
/* @internal */
export interface TypesRegistryResponse extends TypingInstallerResponse {
readonly kind: EventTypesRegistry;
readonly typesRegistry: MapLike<void>;
readonly typesRegistry: MapLike<MapLike<string>>;
}

export interface PackageInstalledResponse extends ProjectResponse {
Expand Down
12 changes: 6 additions & 6 deletions src/server/typingsInstaller/nodeTypingsInstaller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,15 @@ namespace ts.server.typingsInstaller {
}

interface TypesRegistryFile {
entries: MapLike<void>;
entries: MapLike<MapLike<string>>;
}

function loadTypesRegistryFile(typesRegistryFilePath: string, host: InstallTypingHost, log: Log): Map<void> {
function loadTypesRegistryFile(typesRegistryFilePath: string, host: InstallTypingHost, log: Log): Map<MapLike<string>> {
if (!host.fileExists(typesRegistryFilePath)) {
if (log.isEnabled()) {
log.writeLine(`Types registry file '${typesRegistryFilePath}' does not exist`);
}
return createMap<void>();
return createMap<MapLike<string>>();
}
try {
const content = <TypesRegistryFile>JSON.parse(host.readFile(typesRegistryFilePath));
Expand All @@ -59,7 +59,7 @@ namespace ts.server.typingsInstaller {
if (log.isEnabled()) {
log.writeLine(`Error when loading types registry file '${typesRegistryFilePath}': ${(<Error>e).message}, ${(<Error>e).stack}`);
}
return createMap<void>();
return createMap<MapLike<string>>();
}
}

Expand All @@ -77,7 +77,7 @@ namespace ts.server.typingsInstaller {
export class NodeTypingsInstaller extends TypingsInstaller {
private readonly nodeExecSync: ExecSync;
private readonly npmPath: string;
readonly typesRegistry: Map<void>;
readonly typesRegistry: Map<MapLike<string>>;

private delayedInitializationError: InitializationFailedResponse | undefined;

Expand Down Expand Up @@ -141,7 +141,7 @@ namespace ts.server.typingsInstaller {
this.closeProject(req);
break;
case "typesRegistry": {
const typesRegistry: { [key: string]: void } = {};
const typesRegistry: { [key: string]: MapLike<string> } = {};
this.typesRegistry.forEach((value, key) => {
typesRegistry[key] = value;
});
Expand Down
57 changes: 40 additions & 17 deletions src/server/typingsInstaller/typingsInstaller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/// <reference path="../../compiler/core.ts" />
/// <reference path="../../compiler/moduleNameResolver.ts" />
/// <reference path="../../services/jsTyping.ts"/>
/// <reference path="../../services/semver.ts"/>
/// <reference path="../types.ts"/>
/// <reference path="../shared.ts"/>

Expand All @@ -9,6 +10,10 @@ namespace ts.server.typingsInstaller {
devDependencies: MapLike<any>;
}

interface NpmLock {
dependencies: { [packageName: string]: { version: string } };
}

export interface Log {
isEnabled(): boolean;
writeLine(text: string): void;
Expand Down Expand Up @@ -42,7 +47,7 @@ namespace ts.server.typingsInstaller {
}

export abstract class TypingsInstaller {
private readonly packageNameToTypingLocation: Map<string> = createMap<string>();
private readonly packageNameToTypingLocation: Map<JsTyping.CachedTyping> = createMap<JsTyping.CachedTyping>();
private readonly missingTypingsSet: Map<true> = createMap<true>();
private readonly knownCachesSet: Map<true> = createMap<true>();
private readonly projectWatchers: Map<FileWatcher[]> = createMap<FileWatcher[]>();
Expand All @@ -52,7 +57,7 @@ namespace ts.server.typingsInstaller {
private installRunCount = 1;
private inFlightRequestCount = 0;

abstract readonly typesRegistry: Map<void>;
abstract readonly typesRegistry: Map<MapLike<string>>;

constructor(
protected readonly installTypingHost: InstallTypingHost,
Expand Down Expand Up @@ -117,7 +122,8 @@ namespace ts.server.typingsInstaller {
this.safeList,
this.packageNameToTypingLocation,
req.typeAcquisition,
req.unresolvedImports);
req.unresolvedImports,
this.typesRegistry);

if (this.log.isEnabled()) {
this.log.writeLine(`Finished typings discovery: ${JSON.stringify(discoverTypingsResult)}`);
Expand Down Expand Up @@ -156,23 +162,30 @@ namespace ts.server.typingsInstaller {
if (this.log.isEnabled()) {
this.log.writeLine(`Processing cache location '${cacheLocation}'`);
}
if (this.knownCachesSet.get(cacheLocation)) {
if (this.knownCachesSet.has(cacheLocation)) {
if (this.log.isEnabled()) {
this.log.writeLine(`Cache location was already processed...`);
}
return;
}
const packageJson = combinePaths(cacheLocation, "package.json");
const packageLockJson = combinePaths(cacheLocation, "package-lock.json");
if (this.log.isEnabled()) {
this.log.writeLine(`Trying to find '${packageJson}'...`);
}
if (this.installTypingHost.fileExists(packageJson)) {
if (this.installTypingHost.fileExists(packageJson) && this.installTypingHost.fileExists(packageLockJson)) {
const npmConfig = <NpmConfig>JSON.parse(this.installTypingHost.readFile(packageJson));
const npmLock = <NpmLock>JSON.parse(this.installTypingHost.readFile(packageLockJson));
if (this.log.isEnabled()) {
this.log.writeLine(`Loaded content of '${packageJson}': ${JSON.stringify(npmConfig)}`);
this.log.writeLine(`Loaded content of '${packageLockJson}'`);
}
if (npmConfig.devDependencies) {
if (npmConfig.devDependencies && npmLock.dependencies) {
for (const key in npmConfig.devDependencies) {
if (!hasProperty(npmLock.dependencies, key)) {
// if package in package.json but not package-lock.json, skip adding to cache so it is reinstalled on next use
continue;
}
// key is @types/<package name>
const packageName = getBaseFileName(key);
if (!packageName) {
Expand All @@ -184,18 +197,23 @@ namespace ts.server.typingsInstaller {
continue;
}
const existingTypingFile = this.packageNameToTypingLocation.get(packageName);
if (existingTypingFile === typingFile) {
continue;
}
if (existingTypingFile) {
if (existingTypingFile.typingLocation === typingFile) {
continue;
}

if (this.log.isEnabled()) {
this.log.writeLine(`New typing for package ${packageName} from '${typingFile}' conflicts with existing typing file '${existingTypingFile}'`);
}
}
if (this.log.isEnabled()) {
this.log.writeLine(`Adding entry into typings cache: '${packageName}' => '${typingFile}'`);
}
this.packageNameToTypingLocation.set(packageName, typingFile);
const info = getProperty(npmLock.dependencies, key);
const version = info && info.version;
const semver = Semver.parse(version);
const newTyping: JsTyping.CachedTyping = { typingLocation: typingFile, version: semver };
this.packageNameToTypingLocation.set(packageName, newTyping);
}
}
}
Expand All @@ -211,10 +229,6 @@ namespace ts.server.typingsInstaller {
if (this.log.isEnabled()) this.log.writeLine(`'${typing}' is in missingTypingsSet - skipping...`);
return false;
}
if (this.packageNameToTypingLocation.get(typing)) {
if (this.log.isEnabled()) this.log.writeLine(`'${typing}' already has a typing - skipping...`);
return false;
}
const validationResult = JsTyping.validatePackageName(typing);
if (validationResult !== JsTyping.PackageNameValidationResult.Ok) {
// add typing name to missing set so we won't process it again
Expand All @@ -226,6 +240,10 @@ namespace ts.server.typingsInstaller {
if (this.log.isEnabled()) this.log.writeLine(`Entry for package '${typing}' does not exist in local types registry - skipping...`);
return false;
}
if (this.packageNameToTypingLocation.get(typing) && JsTyping.isTypingUpToDate(this.packageNameToTypingLocation.get(typing), this.typesRegistry.get(typing))) {
if (this.log.isEnabled()) this.log.writeLine(`'${typing}' already has an up-to-date typing - skipping...`);
return false;
}
return true;
});
}
Expand Down Expand Up @@ -294,9 +312,12 @@ namespace ts.server.typingsInstaller {
this.missingTypingsSet.set(packageName, true);
continue;
}
if (!this.packageNameToTypingLocation.has(packageName)) {
this.packageNameToTypingLocation.set(packageName, typingFile);
}

// packageName is guaranteed to exist in typesRegistry by filterTypings
const distTags = this.typesRegistry.get(packageName);
const newVersion = Semver.parse(distTags[`ts${ts.versionMajorMinor}`] || distTags[latestDistTag]);
const newTyping: JsTyping.CachedTyping = { typingLocation: typingFile, version: newVersion };
this.packageNameToTypingLocation.set(packageName, newTyping);
installedTypingFiles.push(typingFile);
}
if (this.log.isEnabled()) {
Expand Down Expand Up @@ -390,4 +411,6 @@ namespace ts.server.typingsInstaller {
export function typingsName(packageName: string): string {
return `@types/${packageName}@ts${versionMajorMinor}`;
}

const latestDistTag = "latest";
}
25 changes: 19 additions & 6 deletions src/services/jsTyping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
/// <reference path='../compiler/types.ts' />
/// <reference path='../compiler/core.ts' />
/// <reference path='../compiler/commandLineParser.ts' />
/// <reference path='../services/semver.ts' />

/* @internal */
namespace ts.JsTyping {
Expand All @@ -26,6 +27,17 @@ namespace ts.JsTyping {
typings?: string;
}

export interface CachedTyping {
typingLocation: string;
version: Semver;
}

/* @internal */
export function isTypingUpToDate(cachedTyping: JsTyping.CachedTyping, availableTypingVersions: MapLike<string>) {
const availableVersion = Semver.parse(getProperty(availableTypingVersions, `ts${ts.versionMajorMinor}`) || getProperty(availableTypingVersions, "latest"));
return !availableVersion.greaterThan(cachedTyping.version);
}

/* @internal */
export const nodeCoreModuleList: ReadonlyArray<string> = [
"buffer", "querystring", "events", "http", "cluster",
Expand Down Expand Up @@ -60,7 +72,7 @@ namespace ts.JsTyping {
* @param fileNames are the file names that belong to the same project
* @param projectRootPath is the path to the project root directory
* @param safeListPath is the path used to retrieve the safe list
* @param packageNameToTypingLocation is the map of package names to their cached typing locations
* @param packageNameToTypingLocation is the map of package names to their cached typing locations and installed versions
* @param typeAcquisition is used to customize the typing acquisition process
* @param compilerOptions are used as a source for typing inference
*/
Expand All @@ -70,9 +82,10 @@ namespace ts.JsTyping {
fileNames: string[],
projectRootPath: Path,
safeList: SafeList,
packageNameToTypingLocation: ReadonlyMap<string>,
packageNameToTypingLocation: ReadonlyMap<CachedTyping>,
typeAcquisition: TypeAcquisition,
unresolvedImports: ReadonlyArray<string>):
unresolvedImports: ReadonlyArray<string>,
typesRegistry: ReadonlyMap<MapLike<string>>):
{ cachedTypingPaths: string[], newTypingNames: string[], filesToWatch: string[] } {

if (!typeAcquisition || !typeAcquisition.enable) {
Expand Down Expand Up @@ -122,9 +135,9 @@ namespace ts.JsTyping {
addInferredTypings(module, "Inferred typings from unresolved imports");
}
// Add the cached typing locations for inferred typings that are already installed
packageNameToTypingLocation.forEach((typingLocation, name) => {
if (inferredTypings.has(name) && inferredTypings.get(name) === undefined) {
inferredTypings.set(name, typingLocation);
packageNameToTypingLocation.forEach((typing, name) => {
if (inferredTypings.has(name) && inferredTypings.get(name) === undefined && isTypingUpToDate(typing, typesRegistry.get(name))) {
inferredTypings.set(name, typing.typingLocation);
}
});

Expand Down
Loading