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

chore: migrate jest-haste-map to TypeScript #7854

Merged
merged 16 commits into from Feb 12, 2019
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -25,6 +25,7 @@
- `[jest-watcher]`: Migrate to TypeScript ([#7843](https://github.com/facebook/jest/pull/7843))
- `[jest-mock]`: Migrate to TypeScript ([#7847](https://github.com/facebook/jest/pull/7847), [#7850](https://github.com/facebook/jest/pull/7850))
- `[jest-worker]`: Migrate to TypeScript ([#7853](https://github.com/facebook/jest/pull/7853))
- `[jest-haste-map]`: Migrate to TypeScript ([#7854](https://github.com/facebook/jest/pull/7854))

### Performance

Expand Down
1 change: 0 additions & 1 deletion packages/jest-cli/src/TestSequencer.js
Expand Up @@ -12,7 +12,6 @@ import type {Context} from 'types/Context';
import type {Test} from 'types/TestRunner';

import fs from 'fs';
// $FlowFixMe: Missing ESM export
import {getCacheFilePath} from 'jest-haste-map';

const FAIL = 0;
Expand Down
3 changes: 3 additions & 0 deletions packages/jest-haste-map/package.json
Expand Up @@ -8,7 +8,9 @@
},
"license": "MIT",
"main": "build/index.js",
"types": "build/index.d.ts",
"dependencies": {
"@jest/types": "^24.1.0",
"fb-watchman": "^2.0.0",
"graceful-fs": "^4.1.15",
"invariant": "^2.2.4",
Expand All @@ -23,6 +25,7 @@
"@types/graceful-fs": "^4.1.2",
"@types/invariant": "^2.2.29",
"@types/micromatch": "^3.1.0",
"@types/node": "^10.12.24",
"@types/sane": "^2.0.0"
},
"engines": {
Expand Down
Expand Up @@ -3,66 +3,63 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import type {Glob, Path} from 'types/Config';
import type {FileData} from 'types/HasteMap';

import micromatch from 'micromatch';
import {replacePathSepForGlob} from 'jest-util';
import {Config} from '@jest/types';
import {FileData} from './types';
import * as fastPath from './lib/fast_path';
import micromatch from 'micromatch';
import H from './constants';

export default class HasteFS {
_rootDir: Path;
_files: FileData;
private readonly _rootDir: Config.Path;
private readonly _files: FileData;

constructor({rootDir, files}: {rootDir: Path, files: FileData}) {
constructor({rootDir, files}: {rootDir: Config.Path; files: FileData}) {
this._rootDir = rootDir;
this._files = files;
}

getModuleName(file: Path): ?string {
getModuleName(file: Config.Path): string | null {
const fileMetadata = this._getFileData(file);
return (fileMetadata && fileMetadata[H.ID]) || null;
}

getSize(file: Path): ?number {
getSize(file: Config.Path): number | null {
const fileMetadata = this._getFileData(file);
return (fileMetadata && fileMetadata[H.SIZE]) || null;
}

getDependencies(file: Path): ?Array<string> {
getDependencies(file: Config.Path): Array<string> | null {
const fileMetadata = this._getFileData(file);
return (fileMetadata && fileMetadata[H.DEPENDENCIES]) || null;
}

getSha1(file: Path): ?string {
getSha1(file: Config.Path): string | null {
const fileMetadata = this._getFileData(file);
return (fileMetadata && fileMetadata[H.SHA1]) || null;
}

exists(file: Path): boolean {
exists(file: Config.Path): boolean {
return this._getFileData(file) != null;
}

getAllFiles(): Array<string> {
getAllFiles(): Array<Config.Path> {
return Array.from(this.getAbsoluteFileIterator());
}

getFileIterator(): Iterator<string> {
getFileIterator(): Iterable<Config.Path> {
return this._files.keys();
}

*getAbsoluteFileIterator(): Iterator<string> {
for (const file of this._files.keys()) {
*getAbsoluteFileIterator(): Iterable<Config.Path> {
for (const file of this.getFileIterator()) {
yield fastPath.resolve(this._rootDir, file);
}
}

matchFiles(pattern: RegExp | string): Array<Path> {
matchFiles(pattern: RegExp | string): Array<Config.Path> {
if (!(pattern instanceof RegExp)) {
pattern = new RegExp(pattern);
}
Expand All @@ -75,7 +72,10 @@ export default class HasteFS {
return files;
}

matchFilesWithGlob(globs: Array<Glob>, root: ?Path): Set<Path> {
matchFilesWithGlob(
globs: Array<Config.Glob>,
root: Config.Path | null,
): Set<Config.Path> {
const files = new Set();
for (const file of this.getAbsoluteFileIterator()) {
const filePath = root ? fastPath.relative(root, file) : file;
Expand All @@ -86,7 +86,7 @@ export default class HasteFS {
return files;
}

_getFileData(file: Path) {
private _getFileData(file: Config.Path) {
const relativePath = fastPath.relative(this._rootDir, file);
return this._files.get(relativePath);
}
Expand Down
Expand Up @@ -3,53 +3,49 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

import type {Path} from 'types/Config';
import type {
import {Config} from '@jest/types';
import {
DuplicatesSet,
HTypeValue,
ModuleMetaData,
RawModuleMap,
ModuleMapData,
DuplicatesIndex,
MockData,
} from 'types/HasteMap';
} from './types';

import * as fastPath from './lib/fast_path';
import H from './constants';

const EMPTY_OBJ = {};
const EMPTY_OBJ = {} as {[key: string]: any};
const EMPTY_MAP = new Map();

export opaque type SerializableModuleMap = {
// There is no easier way to extract the type of the entries of a Map
duplicates: $Call<
typeof Array.from,
$Call<$PropertyType<DuplicatesIndex, 'entries'>>,
>,
map: $Call<typeof Array.from, $Call<$PropertyType<ModuleMapData, 'entries'>>>,
mocks: $Call<typeof Array.from, $Call<$PropertyType<MockData, 'entries'>>>,
rootDir: string,
type ValueType<T> = T extends Map<string, infer V> ? V : never;

export type SerializableModuleMap = {
duplicates: ReadonlyArray<[string, ValueType<DuplicatesIndex>]>;
map: ReadonlyArray<[string, ValueType<ModuleMapData>]>;
mocks: ReadonlyArray<[string, ValueType<MockData>]>;
rootDir: Config.Path;
};

export default class ModuleMap {
_raw: RawModuleMap;
static DuplicateHasteCandidatesError: Class<DuplicateHasteCandidatesError>;
private readonly _raw: RawModuleMap;
static DuplicateHasteCandidatesError: typeof DuplicateHasteCandidatesError;

constructor(raw: RawModuleMap) {
this._raw = raw;
}

getModule(
name: string,
platform: ?string,
supportsNativePlatform: ?boolean,
type: ?HTypeValue,
): ?Path {
if (!type) {
platform: string | null,
supportsNativePlatform: boolean | null,
type: HTypeValue | null,
): Config.Path | null {
if (type == null) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the types are right, then

Suggested change
if (type == null) {
if (type === null) {

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We only do getModule (outside of unit tests) right below: return this.getModule(name, platform, null, H.PACKAGE);

So I wonder if it's never null either? May be that FB uses it internally though, who knows

type = H.MODULE;
}
const module = this._getModuleMetadata(
Expand All @@ -66,13 +62,13 @@ export default class ModuleMap {

getPackage(
name: string,
platform: ?string,
supportsNativePlatform: ?boolean,
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure why we pass null to getModule instead of using this argument

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea, just wanted to comment about it. maybe we should start passing it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDK. @rubennorte?

): ?Path {
platform: string | null,
_supportsNativePlatform: boolean | null,
): Config.Path | null {
return this.getModule(name, platform, null, H.PACKAGE);
}

getMockModule(name: string): ?Path {
getMockModule(name: string): Config.Path | undefined {
const mockPath =
this._raw.mocks.get(name) || this._raw.mocks.get(name + '/index');
return mockPath && fastPath.resolve(this._raw.rootDir, mockPath);
Expand Down Expand Up @@ -113,11 +109,11 @@ export default class ModuleMap {
* extra sure. If metadata exists both in the `duplicates` object and the
* `map`, this would be a bug.
*/
_getModuleMetadata(
private _getModuleMetadata(
name: string,
platform: ?string,
platform: string | null,
supportsNativePlatform: boolean,
): ?ModuleMetaData {
): ModuleMetaData | null {
const map = this._raw.map.get(name) || EMPTY_OBJ;
const dupMap = this._raw.duplicates.get(name) || EMPTY_MAP;
if (platform != null) {
Expand Down Expand Up @@ -154,11 +150,11 @@ export default class ModuleMap {
return null;
}

_assertNoDuplicates(
private _assertNoDuplicates(
name: string,
platform: string,
supportsNativePlatform: boolean,
relativePathSet: ?DuplicatesSet,
relativePathSet: DuplicatesSet | null,
) {
if (relativePathSet == null) {
return;
Expand All @@ -180,7 +176,7 @@ export default class ModuleMap {
);
}

static create(rootDir: Path) {
static create(rootDir: string) {
return new ModuleMap({
duplicates: new Map(),
map: new Map(),
Expand All @@ -192,7 +188,7 @@ export default class ModuleMap {

class DuplicateHasteCandidatesError extends Error {
hasteName: string;
platform: ?string;
platform: string | null;
supportsNativePlatform: boolean;
duplicatesSet: DuplicatesSet;

Expand Down
6 changes: 2 additions & 4 deletions packages/jest-haste-map/src/__tests__/index.test.js
Expand Up @@ -6,10 +6,8 @@
*
*/

'use strict';

import crypto from 'crypto';
import {skipSuiteOnWindows} from '../../../../scripts/ConditionalTest';
const crypto = require('crypto');

function mockHashContents(contents) {
return crypto
Expand Down Expand Up @@ -75,7 +73,7 @@ jest.mock('sane', () => ({
WatchmanWatcher: mockWatcherConstructor,
}));

jest.mock('../lib/WatchmanWatcher.js', () => mockWatcherConstructor);
jest.mock('../lib/WatchmanWatcher', () => mockWatcherConstructor);

let mockChangedFiles;
let mockFs;
Expand Down
Expand Up @@ -3,8 +3,6 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/

// This list is compiled after the MDN list of the most common MIME types (see
Expand Down
Expand Up @@ -3,8 +3,6 @@
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
*/
/* eslint-disable sort-keys */

Expand All @@ -16,7 +14,10 @@
* This constant key map allows to keep the map smaller without having to build
* a custom serialization library.
*/
export default {

import {HType} from './types';

const constants: HType = {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Explicitly typing this ensure the type remains narrow rather than expanded to number

/* file map attributes */
ID: 0,
MTIME: 1,
Expand All @@ -37,3 +38,5 @@ export default {
GENERIC_PLATFORM: 'g',
NATIVE_PLATFORM: 'native',
};

export default constants;