Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
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
39 changes: 38 additions & 1 deletion src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -559,6 +559,12 @@ namespace ts {
sourceMap: false,
};

interface OutputFingerprint {
Copy link
Member

Choose a reason for hiding this comment

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

Mark this /* @internal */

Copy link
Author

Choose a reason for hiding this comment

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

@DanielRosenwasser I am confused, it's not even exported, why does it need /** @internal */?

Copy link
Member

Choose a reason for hiding this comment

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

Whoops! Misread the code, forgive me. I was taking a quick glance at this PR again since it's been a bit.

hash: string;
byteOrderMark: boolean;
mtime: Date;
}

export function createCompilerHost(options: CompilerOptions, setParentNodes?: boolean): CompilerHost {
const existingDirectories: Map<boolean> = {};

Expand Down Expand Up @@ -609,11 +615,42 @@ namespace ts {
}
}

const outputFingerprints: Map<OutputFingerprint> =
options.watch && sys.createHash && sys.getModifiedTime ? {} : undefined;

const fileWriter: typeof sys.writeFile = outputFingerprints ?
Copy link
Contributor

Choose a reason for hiding this comment

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

why not just a function?

function writeFile(fileName, data, writeByteOrderMark) {
    if (outputFingerprints) {
        const hash = sys.createHash(data);
        const mtimeBefore = sys.getModifiedTime(fileName);

        if (mtimeBefore && hasProperty(outputFingerprints, fileName)) {
            const fingerprint = outputFingerprints[fileName];

            // If output has not been changed, and the file has no external modification
            if (fingerprint.byteOrderMark === writeByteOrderMark &&
                fingerprint.hash === hash &&
                fingerprint.mtime.getTime() === mtimeBefore.getTime()) {
                return;
            }
        }
    }

    sys.writeFile(fileName, data, writeByteOrderMark);

    if (outputFingerprints) {
        const mtimeAfter = sys.getModifiedTime(fileName);

        outputFingerprints[fileName] = {
            hash,
            byteOrderMark: writeByteOrderMark,
            mtime: mtimeAfter
        };
    }
}

Copy link
Author

Choose a reason for hiding this comment

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

@mhegazy I personally do not like parallel branches with same condition (because they can not indicate a strong relationship between those branches), so if I write them into a single function, I would like a single if...else with two sys.writeFile in each branch. And after merging them together, function fileWriter would be awkward to exist alone with writeFile. If I inline fileWriter into writeFile, it would result in a long try block. 😢 What's your suggestion?

Copy link
Contributor

Choose a reason for hiding this comment

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

what about two functions then:

const outputFingerprints: Map<OutputFingerprint>;


function writeFile(fileName, data, writeByteOrderMark) {
    if (options.watch && sys.createHash && sys.getModifiedTime) {
        writeFileIfUpdated(fileName, data, writeByteOrderMark);
    }
    else {
        sys.writeFile(fileName, data, writeByteOrderMark);
    }
}

function writeFileIfUpdated(fileName, data, writeByteOrderMark) {
    if (!outputFingerprints) {
        outputFingerprints = {};
    }

    const hash = sys.createHash(data);
    const mtimeBefore = sys.getModifiedTime(fileName);

    if (mtimeBefore && hasProperty(outputFingerprints, fileName)) {
        const fingerprint = outputFingerprints[fileName];

        // If output has not been changed, and the file has no external modification
        if (fingerprint.byteOrderMark === writeByteOrderMark &&
            fingerprint.hash === hash &&
            fingerprint.mtime.getTime() === mtimeBefore.getTime()) {
            return;
        }

        sys.writeFile(fileName, data, writeByteOrderMark);

        const mtimeAfter = sys.getModifiedTime(fileName);

        outputFingerprints[fileName] = {
            hash,
            byteOrderMark: writeByteOrderMark,
            mtime: mtimeAfter
        };
    }
}

(fileName, data, writeByteOrderMark) => {
const hash = sys.createHash(data);
const mtimeBefore = sys.getModifiedTime(fileName);

if (mtimeBefore && outputFingerprints.hasOwnProperty(fileName)) {
Copy link
Member

Choose a reason for hiding this comment

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

Use the hasProperty function

const fingerprint = outputFingerprints[fileName];

// If output has not been changed, and the file has no external modification
if (fingerprint.byteOrderMark === writeByteOrderMark &&
fingerprint.hash === hash &&
fingerprint.mtime.getTime() === mtimeBefore.getTime()) {
return;
}
}

sys.writeFile(fileName, data, writeByteOrderMark);

const mtimeAfter = sys.getModifiedTime(fileName);

outputFingerprints[fileName] = {
hash,
byteOrderMark: writeByteOrderMark,
mtime: mtimeAfter
};
} :
(fileName, data, writeByteOrderMark) => sys.writeFile(fileName, data, writeByteOrderMark);

function writeFile(fileName: string, data: string, writeByteOrderMark: boolean, onError?: (message: string) => void) {
try {
const start = new Date().getTime();
ensureDirectoriesExist(getDirectoryPath(normalizePath(fileName)));
sys.writeFile(fileName, data, writeByteOrderMark);
fileWriter(fileName, data, writeByteOrderMark);
ioWriteTime += new Date().getTime() - start;
}
catch (e) {
Expand Down
11 changes: 11 additions & 0 deletions src/compiler/sys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ namespace ts {
getExecutingFilePath(): string;
getCurrentDirectory(): string;
readDirectory(path: string, extension?: string, exclude?: string[]): string[];
getModifiedTime?(path: string): Date;
createHash?(data: string): string;
getMemoryUsage?(): number;
exit(exitCode?: number): void;
}
Expand Down Expand Up @@ -226,6 +228,7 @@ namespace ts {
const _fs = require("fs");
const _path = require("path");
const _os = require("os");
const _crypto = require("crypto");

// average async stat takes about 30 microseconds
// set chunk size to do 30 files in < 1 millisecond
Expand Down Expand Up @@ -556,6 +559,14 @@ namespace ts {
return process.cwd();
},
readDirectory,
getModifiedTime(path) {
return _fs.existsSync(path) && _fs.statSync(path).mtime;
Copy link
Member

Choose a reason for hiding this comment

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

I this should be

let stat: any;
try {
    stat = _fs.statSync(path);
}
catch (e) {
    return undefined;
}

return stat.mtime;

Copy link
Author

Choose a reason for hiding this comment

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

@DanielRosenwasser Can I put .mtime into try block and return it directly?

Copy link
Member

Choose a reason for hiding this comment

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

I considered this, but I was thinking that if for some reason statSync returned undefined, we'd want to know about it. Thinking about it now, I think it might be better to return undefined unless someone else has other thoughts.

},
createHash(data) {
const hash = _crypto.createHash("md5");
Copy link
Member

Choose a reason for hiding this comment

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

Do we always want to use MD5 @mhegazy?

Also, could you get away with a single call to createHash?

if (!hash) {
    hash = _crypto.createHash("md5");
}

hash.update(data);
return hash.digest("hex");

Copy link
Author

Choose a reason for hiding this comment

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

@DanielRosenwasser Seems that we need to createHash every time.

hash.update(data);
return hash.digest("hex");
},
getMemoryUsage() {
if (global.gc) {
global.gc();
Expand Down