Remove timestamp checking and move registry check into jstyping

This commit is contained in:
uniqueiniquity 2018-01-11 11:11:26 -08:00
parent e72ea6f7b1
commit a21f73f862
5 changed files with 46 additions and 123 deletions

View File

@ -1081,16 +1081,6 @@ namespace ts.projectSystem {
}
})
};
const date = new Date();
date.setFullYear(date.getFullYear() - 1);
const timestamps = {
path: "/a/data/timestamps.json",
content: JSON.stringify({
entries: {
"@types/jquery": date.getTime()
}
})
};
const jquery = {
path: "/a/data/node_modules/@types/jquery/index.d.ts",
content: "declare const $: { x: number }"
@ -1102,11 +1092,21 @@ namespace ts.projectSystem {
"types-registry": "^0.1.317"
},
devDependencies: {
"@types/jquery": "^3.2.16"
"@types/jquery": "^1.0.0"
}
})
};
const host = createServerHost([file1, packageJson, jquery, timestamps, cacheConfig]);
const cacheLockConfig = {
path: "/a/data/package-lock.json",
content: JSON.stringify({
dependencies: {
"@types/jquery": {
version: "1.0.0"
}
}
})
};
const host = createServerHost([file1, packageJson, jquery, cacheConfig, cacheLockConfig]);
const installer = new (class extends Installer {
constructor() {
super(host, { typesRegistry: createTypesRegistry("jquery") });
@ -1129,7 +1129,6 @@ namespace ts.projectSystem {
checkNumberOfProjects(projectService, { inferredProjects: 1 });
checkProjectActualFiles(p, [file1.path, jquery.path]);
assert(host.readFile(timestamps.path) !== JSON.stringify({ entries: { "@types/jquery": date.getTime() } }), "timestamps content should be updated");
});
it("non-expired cache entry (inferred project, should not install typings)", () => {
@ -1282,7 +1281,7 @@ namespace ts.projectSystem {
const host = createServerHost([app, jquery, chroma]);
const logger = trackingLogger();
const result = JsTyping.discoverTypings(host, logger.log, [app.path, jquery.path, chroma.path], getDirectoryPath(<Path>app.path), safeList, emptyMap, { enable: true }, emptyArray);
const result = JsTyping.discoverTypings(host, logger.log, [app.path, jquery.path, chroma.path], getDirectoryPath(<Path>app.path), safeList, emptyMap, { enable: true }, emptyArray, emptyMap);
const finish = logger.finish();
assert.deepEqual(finish, [
'Inferred typings from file names: ["jquery","chroma-js"]',
@ -1302,7 +1301,7 @@ namespace ts.projectSystem {
for (const name of JsTyping.nodeCoreModuleList) {
const logger = trackingLogger();
const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(<Path>f.path), emptySafeList, cache, { enable: true }, [name, "somename"]);
const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(<Path>f.path), emptySafeList, cache, { enable: true }, [name, "somename"], emptyMap);
assert.deepEqual(logger.finish(), [
'Inferred typings from unresolved imports: ["node","somename"]',
'Result: {"cachedTypingPaths":[],"newTypingNames":["node","somename"],"filesToWatch":["/a/b/bower_components","/a/b/node_modules"]}',
@ -1321,9 +1320,10 @@ namespace ts.projectSystem {
content: ""
};
const host = createServerHost([f, node]);
const cache = createMapFromTemplate<JsTyping.CachedTyping>({ node: { typingLocation: node.path, timestamp: Date.now(), version: new Semver(1, 0, 0, /*isPrerelease*/ false) } });
const cache = createMapFromTemplate<JsTyping.CachedTyping>({ node: { typingLocation: node.path, version: new Semver(1, 3, 0, /*isPrerelease*/ false) } });
const registry = createTypesRegistry("node");
const logger = trackingLogger();
const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(<Path>f.path), emptySafeList, cache, { enable: true }, ["fs", "bar"]);
const result = JsTyping.discoverTypings(host, logger.log, [f.path], getDirectoryPath(<Path>f.path), emptySafeList, cache, { enable: true }, ["fs", "bar"], registry);
assert.deepEqual(logger.finish(), [
'Inferred typings from unresolved imports: ["node","bar"]',
'Result: {"cachedTypingPaths":["/a/b/node.d.ts"],"newTypingNames":["bar"],"filesToWatch":["/a/b/bower_components","/a/b/node_modules"]}',
@ -1348,7 +1348,7 @@ namespace ts.projectSystem {
const host = createServerHost([app, a, b]);
const cache = createMap<JsTyping.CachedTyping>();
const logger = trackingLogger();
const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(<Path>app.path), emptySafeList, cache, { enable: true }, /*unresolvedImports*/ []);
const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(<Path>app.path), emptySafeList, cache, { enable: true }, /*unresolvedImports*/ [], emptyMap);
assert.deepEqual(logger.finish(), [
'Searching for typing names in /node_modules; all files: ["/node_modules/a/package.json"]',
' Found package names: ["a"]',
@ -1363,9 +1363,6 @@ namespace ts.projectSystem {
});
it("should install expired typings", () => {
const date = new Date();
date.setFullYear(date.getFullYear() - 1);
const app = {
path: "/a/app.js",
content: ""
@ -1381,11 +1378,12 @@ namespace ts.projectSystem {
};
const host = createServerHost([app]);
const cache = createMapFromTemplate<JsTyping.CachedTyping>({
node: { typingLocation: node.path, timestamp: Date.now(), version: new Semver(1, 0, 0, /*isPrerelease*/ false) },
commander: { typingLocation: commander.path, timestamp: date.getTime(), version: new Semver(1, 0, 0, /*isPrerelease*/ false) }
node: { typingLocation: node.path, version: new Semver(1, 3, 0, /*isPrerelease*/ false) },
commander: { typingLocation: commander.path, version: new Semver(1, 0, 0, /*isPrerelease*/ false) }
});
const registry = createTypesRegistry("node", "commander");
const logger = trackingLogger();
const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(<Path>app.path), emptySafeList, cache, { enable: true }, ["http", "commander"]);
const result = JsTyping.discoverTypings(host, logger.log, [app.path], getDirectoryPath(<Path>app.path), emptySafeList, cache, { enable: true }, ["http", "commander"], registry);
assert.deepEqual(logger.finish(), [
'Inferred typings from unresolved imports: ["node","commander"]',
'Result: {"cachedTypingPaths":["/a/cache/node_modules/@types/node/index.d.ts"],"newTypingNames":["commander"],"filesToWatch":["/a/bower_components","/a/node_modules"]}',

View File

@ -128,6 +128,5 @@ declare namespace ts.server {
writeFile(path: string, content: string): void;
createDirectory(path: string): void;
watchFile?(path: string, callback: FileWatcherCallback, pollingInterval?: number): FileWatcher;
getModifiedTime?(path: string): Date;
}
}

View File

@ -46,62 +46,10 @@ namespace ts.server.typingsInstaller {
onRequestCompleted: RequestCompletedAction;
}
const timestampsFileName = "timestamps.json";
type TypingsTimestamps = MapLike<number>;
interface TypeDeclarationTimestampFile {
// entries maps from package names (e.g. "@types/node") to timestamp values (as produced by Date#getTime)
entries: TypingsTimestamps;
}
function loadTypeDeclarationTimestampFile(typeDeclarationTimestampFilePath: string, host: InstallTypingHost, log: Log): TypingsTimestamps {
try {
if (log.isEnabled()) {
log.writeLine("Loading type declaration timestamp file.");
}
const content = <TypeDeclarationTimestampFile>JSON.parse(host.readFile(typeDeclarationTimestampFilePath));
return content.entries || {};
}
catch (e) {
if (log.isEnabled()) {
log.writeLine(`Error when loading type declaration timestamp file '${typeDeclarationTimestampFilePath}': ${(<Error>e).message}, ${(<Error>e).stack}`);
}
// If file cannot be read, we update all requested type declarations.
return {};
}
}
function updateTypeDeclarationTimestampFile(typeDeclarationTimestampFilePath: string, timestampsInProcess: TypingsTimestamps, host: InstallTypingHost, log: Log): TypingsTimestamps {
const timestampsOnDisk = loadTypeDeclarationTimestampFile(typeDeclarationTimestampFilePath, host, log);
for (const packageName in timestampsOnDisk) {
const timestampForPackageInProcess = getProperty(timestampsInProcess, packageName);
if (timestampForPackageInProcess) {
timestampsInProcess[packageName] = Math.max(timestampForPackageInProcess, timestampsOnDisk[packageName]);
}
else {
timestampsInProcess[packageName] = timestampsOnDisk[packageName];
}
}
return timestampsInProcess;
}
function writeTypeDeclarationTimestampFile(typeDeclarationTimestampFilePath: string, newContents: TypeDeclarationTimestampFile, host: InstallTypingHost, log: Log): void {
try {
if (log.isEnabled()) {
log.writeLine("Writing type declaration timestamp file.");
}
host.writeFile(typeDeclarationTimestampFilePath, JSON.stringify(newContents));
}
catch (e) {
if (log.isEnabled()) {
log.writeLine(`Error when writing type declaration timestamp file '${typeDeclarationTimestampFilePath}': ${(<Error>e).message}, ${(<Error>e).stack}`);
}
}
}
export abstract class TypingsInstaller {
private readonly packageNameToTypingLocation: Map<JsTyping.CachedTyping> = createMap<JsTyping.CachedTyping>();
private readonly missingTypingsSet: Map<true> = createMap<true>();
private readonly knownCacheToTimestamps: Map<TypingsTimestamps> = createMap<TypingsTimestamps>();
private readonly knownCachesSet: Map<true> = createMap<true>();
private readonly projectWatchers: Map<FileWatcher[]> = createMap<FileWatcher[]>();
private safeList: JsTyping.SafeList | undefined;
readonly pendingRunRequests: PendingRequest[] = [];
@ -156,12 +104,11 @@ namespace ts.server.typingsInstaller {
}
// load existing typing information from the cache
const timestampsFilePath = combinePaths(req.cachePath || this.globalCachePath, timestampsFileName);
if (req.cachePath) {
if (this.log.isEnabled()) {
this.log.writeLine(`Request specifies cache path '${req.cachePath}', loading cached information...`);
}
this.processCacheLocation(req.cachePath, timestampsFilePath);
this.processCacheLocation(req.cachePath);
}
if (this.safeList === undefined) {
@ -175,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)}`);
@ -186,7 +134,7 @@ namespace ts.server.typingsInstaller {
// install typings
if (discoverTypingsResult.newTypingNames.length) {
this.installTypings(req, req.cachePath || this.globalCachePath, discoverTypingsResult.cachedTypingPaths, discoverTypingsResult.newTypingNames, timestampsFilePath);
this.installTypings(req, req.cachePath || this.globalCachePath, discoverTypingsResult.cachedTypingPaths, discoverTypingsResult.newTypingNames);
}
else {
this.sendResponse(this.createSetTypings(req, discoverTypingsResult.cachedTypingPaths));
@ -210,17 +158,16 @@ namespace ts.server.typingsInstaller {
this.safeList = JsTyping.loadSafeList(this.installTypingHost, this.safeListPath);
}
private processCacheLocation(cacheLocation: string, timestampsFilePath?: string) {
private processCacheLocation(cacheLocation: string) {
if (this.log.isEnabled()) {
this.log.writeLine(`Processing cache location '${cacheLocation}'`);
}
if (this.knownCacheToTimestamps.has(cacheLocation)) {
if (this.knownCachesSet.has(cacheLocation)) {
if (this.log.isEnabled()) {
this.log.writeLine(`Cache location was already processed...`);
}
return;
}
const typeDeclarationTimestamps = loadTypeDeclarationTimestampFile(timestampsFilePath || combinePaths(cacheLocation, timestampsFileName), this.installTypingHost, this.log);
const packageJson = combinePaths(cacheLocation, "package.json");
const packageLockJson = combinePaths(cacheLocation, "package-lock.json");
if (this.log.isEnabled()) {
@ -258,20 +205,10 @@ namespace ts.server.typingsInstaller {
if (this.log.isEnabled()) {
this.log.writeLine(`Adding entry into typings cache: '${packageName}' => '${typingFile}'`);
}
if (getProperty(typeDeclarationTimestamps, key) === undefined) {
// getModifiedTime is only undefined if we were to use the ChakraHost, but we never do in this scenario
// defaults to old behavior of never updating if we ever use a host without getModifiedTime in the future
const timestamp = this.installTypingHost.getModifiedTime === undefined ? Date.now() : this.installTypingHost.getModifiedTime(typingFile).getTime();
typeDeclarationTimestamps[key] = timestamp;
if (this.log.isEnabled()) {
this.log.writeLine(`Adding entry into timestamp cache: '${key}' => '${timestamp}'`);
}
}
const info = getProperty(npmLock.dependencies, key);
const version = info && info.version;
const semver = Semver.parse(version);
// timestamp guaranteed to not be undefined by above check
const newTyping: JsTyping.CachedTyping = { typingLocation: typingFile, timestamp: getProperty(typeDeclarationTimestamps, key), version: semver };
const newTyping: JsTyping.CachedTyping = { typingLocation: typingFile, version: semver };
this.packageNameToTypingLocation.set(packageName, newTyping);
}
}
@ -279,7 +216,7 @@ namespace ts.server.typingsInstaller {
if (this.log.isEnabled()) {
this.log.writeLine(`Finished processing cache location '${cacheLocation}'`);
}
this.knownCacheToTimestamps.set(cacheLocation, typeDeclarationTimestamps);
this.knownCachesSet.set(cacheLocation, true);
}
private filterTypings(typingsToInstall: ReadonlyArray<string>): ReadonlyArray<string> {
@ -299,17 +236,12 @@ 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) && isTypingUpToDate(this.packageNameToTypingLocation.get(typing), this.typesRegistry.get(typing))) {
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;
});
function isTypingUpToDate(cachedTyping: JsTyping.CachedTyping, availableTypingVersions: MapLike<string>) {
const availableVersion = Semver.parse(getProperty(availableTypingVersions, `ts${ts.version}`));
return !availableVersion.greaterThan(cachedTyping.version);
}
}
protected ensurePackageDirectoryExists(directory: string) {
@ -326,7 +258,7 @@ namespace ts.server.typingsInstaller {
}
}
private installTypings(req: DiscoverTypings, cachePath: string, currentlyCachedTypings: string[], typingsToInstall: string[], timestampsFilePath: string) {
private installTypings(req: DiscoverTypings, cachePath: string, currentlyCachedTypings: string[], typingsToInstall: string[]) {
if (this.log.isEnabled()) {
this.log.writeLine(`Installing typings ${JSON.stringify(typingsToInstall)}`);
}
@ -369,9 +301,7 @@ namespace ts.server.typingsInstaller {
if (this.log.isEnabled()) {
this.log.writeLine(`Installed typings ${JSON.stringify(scopedTypings)}`);
}
const typeDeclarationTimestamps = this.knownCacheToTimestamps.get(cachePath);
const installedTypingFiles: string[] = [];
const typesPackageName = (packageName: string) => `@types/${packageName}`;
for (const packageName of filteredTypings) {
const typingFile = typingToFileName(cachePath, packageName, this.installTypingHost, this.log);
if (!typingFile) {
@ -379,22 +309,15 @@ namespace ts.server.typingsInstaller {
continue;
}
const newTimestamp = Date.now();
const newVersion = Semver.parse(this.typesRegistry.get(packageName)[`ts${ts.versionMajorMinor}`]);
const newTyping: JsTyping.CachedTyping = { typingLocation: typingFile, timestamp: newTimestamp, version: newVersion };
const newTyping: JsTyping.CachedTyping = { typingLocation: typingFile, version: newVersion };
this.packageNameToTypingLocation.set(packageName, newTyping);
typeDeclarationTimestamps[typesPackageName(packageName)] = newTimestamp;
installedTypingFiles.push(typingFile);
}
if (this.log.isEnabled()) {
this.log.writeLine(`Installed typing files ${JSON.stringify(installedTypingFiles)}`);
}
const updatedTypeDeclarationTimestamps = updateTypeDeclarationTimestampFile(timestampsFilePath, typeDeclarationTimestamps, this.installTypingHost, this.log);
const newFileContents: TypeDeclarationTimestampFile = { entries: updatedTypeDeclarationTimestamps };
writeTypeDeclarationTimestampFile(timestampsFilePath, newFileContents, this.installTypingHost, this.log);
this.knownCacheToTimestamps.set(cachePath, updatedTypeDeclarationTimestamps);
this.sendResponse(this.createSetTypings(req, currentlyCachedTypings.concat(installedTypingFiles)));
}
finally {

View File

@ -29,13 +29,13 @@ namespace ts.JsTyping {
export interface CachedTyping {
typingLocation: string;
timestamp: number;
version: Semver;
}
export function isTypingExpired(typing: JsTyping.CachedTyping | undefined) {
const msPerMonth = 1000 * 60 * 60 * 24 * 30; // ms/second * second/minute * minutes/hour * hours/day * days/month
return !typing || typing.timestamp < Date.now() - msPerMonth;
/* @internal */
export function isTypingUpToDate(cachedTyping: JsTyping.CachedTyping, availableTypingVersions: MapLike<string>) {
const availableVersion = Semver.parse(getProperty(availableTypingVersions, `ts${ts.versionMajorMinor}`));
return !availableVersion.greaterThan(cachedTyping.version);
}
/* @internal */
@ -72,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 and time of caching and versions
* @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
*/
@ -84,7 +84,8 @@ namespace ts.JsTyping {
safeList: SafeList,
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) {
@ -135,7 +136,7 @@ namespace ts.JsTyping {
}
// Add the cached typing locations for inferred typings that are already installed
packageNameToTypingLocation.forEach((typing, name) => {
if (inferredTypings.has(name) && inferredTypings.get(name) === undefined && !isTypingExpired(typing)) {
if (inferredTypings.has(name) && inferredTypings.get(name) === undefined && isTypingUpToDate(typing, typesRegistry.get(name))) {
inferredTypings.set(name, typing.typingLocation);
}
});

View File

@ -28,10 +28,11 @@ namespace ts {
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<JsTyping.CachedTyping>; // The map of package names to their cached typing locations
packageNameToTypingLocation: Map<JsTyping.CachedTyping>; // The map of package names to their cached typing locations and installed versions
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
typesRegistry: ReadonlyMap<MapLike<string>>; // The map of available typings in npm to maps of TS versions to their latest supported versions
}
export interface ScriptSnapshotShim {
@ -1171,7 +1172,8 @@ namespace ts {
this.safeList,
info.packageNameToTypingLocation,
info.typeAcquisition,
info.unresolvedImports);
info.unresolvedImports,
info.typesRegistry);
});
}
}