From 3836323724f1a1c22593266f8438a607321e44ff Mon Sep 17 00:00:00 2001 From: Jakob Ackermann Date: Wed, 7 Aug 2024 16:51:44 +0200 Subject: [PATCH] Merge pull request #19817 from overleaf/jpa-types [overleaf-editor-core] stronger type checking via web GitOrigin-RevId: 427019f40e2905f2e0ec11dc09f5fccdbb1f905b --- libraries/overleaf-editor-core/lib/comment.js | 1 + libraries/overleaf-editor-core/lib/file.js | 22 ++++- .../lib/file_data/binary_file_data.js | 17 +++- .../lib/file_data/hash_file_data.js | 14 ++- .../lib/file_data/hollow_binary_file_data.js | 13 ++- .../lib/file_data/hollow_string_file_data.js | 13 ++- .../lib/file_data/index.js | 52 +++++++---- .../lib/file_data/lazy_string_file_data.js | 30 +++++- .../lib/file_data/string_file_data.js | 7 ++ .../lib/file_data/tracking_props.js | 5 + .../overleaf-editor-core/lib/file_map.js | 91 ++++++++++++++++--- libraries/overleaf-editor-core/lib/label.js | 14 ++- .../lib/operation/add_comment_operation.js | 1 + .../lib/operation/edit_file_operation.js | 16 +++- .../lib/operation/edit_no_operation.js | 6 +- .../lib/operation/edit_operation.js | 3 +- .../lib/operation/scan_op.js | 39 +++++++- .../operation/set_comment_state_operation.js | 4 +- .../lib/operation/text_operation.js | 19 +++- libraries/overleaf-editor-core/lib/range.js | 14 +++ .../overleaf-editor-core/lib/safe_pathname.js | 3 +- .../overleaf-editor-core/lib/snapshot.js | 60 +++++++++--- libraries/overleaf-editor-core/lib/types.ts | 51 ++++++++++- .../lib/v2_doc_versions.js | 15 ++- libraries/overleaf-editor-core/package.json | 10 +- package-lock.json | 30 ++++++ .../storage/lib/blob_store/index.js | 2 +- ...ort-overleaf-editor-core-for-type-check.ts | 1 + services/web/package.json | 1 + 29 files changed, 470 insertions(+), 84 deletions(-) create mode 100644 services/web/frontend/js/features/full-project-on-client/import-overleaf-editor-core-for-type-check.ts diff --git a/libraries/overleaf-editor-core/lib/comment.js b/libraries/overleaf-editor-core/lib/comment.js index baf0814f51..bd1a46d21a 100644 --- a/libraries/overleaf-editor-core/lib/comment.js +++ b/libraries/overleaf-editor-core/lib/comment.js @@ -149,6 +149,7 @@ class Comment { * @returns {CommentRawData} */ toRaw() { + /** @type CommentRawData */ const raw = { id: this.id, ranges: this.ranges.map(range => range.toRaw()), diff --git a/libraries/overleaf-editor-core/lib/file.js b/libraries/overleaf-editor-core/lib/file.js index 9e0f419ad0..01cef3d273 100644 --- a/libraries/overleaf-editor-core/lib/file.js +++ b/libraries/overleaf-editor-core/lib/file.js @@ -13,6 +13,8 @@ const StringFileData = require('./file_data/string_file_data') * @typedef {import("./blob")} Blob * @typedef {import("./types").BlobStore} BlobStore * @typedef {import("./types").ReadonlyBlobStore} ReadonlyBlobStore + * @typedef {import("./types").RawFileData} RawFileData + * @typedef {import("./types").RawFile} RawFile * @typedef {import("./types").StringFileRawData} StringFileRawData * @typedef {import("./types").CommentRawData} CommentRawData * @typedef {import("./file_data/comment_list")} CommentList @@ -62,9 +64,14 @@ class File { assert.instance(data, FileData, 'File: bad data') this.data = data + this.metadata = {} this.setMetadata(metadata || {}) } + /** + * @param {RawFile} raw + * @return {File|null} + */ static fromRaw(raw) { if (!raw) return null return new File(FileData.fromRaw(raw), raw.metadata) @@ -90,8 +97,8 @@ class File { } /** - * @param {number} [byteLength] - * @param {number} [stringLength] + * @param {number} byteLength + * @param {number?} stringLength * @param {Object} [metadata] * @return {File} */ @@ -109,7 +116,11 @@ class File { return new File(FileData.createLazyFromBlobs(blob, rangesBlob), metadata) } + /** + * @returns {RawFile} + */ toRaw() { + /** @type RawFile */ const rawFileData = this.data.toRaw() storeRawMetadata(this.metadata, rawFileData) return rawFileData @@ -249,15 +260,20 @@ class File { * the hash. * * @param {BlobStore} blobStore - * @return {Promise} a raw HashFile + * @return {Promise} a raw HashFile */ async store(blobStore) { + /** @type RawFile */ const raw = await this.data.store(blobStore) storeRawMetadata(this.metadata, raw) return raw } } +/** + * @param {Object} metadata + * @param {RawFile} raw + */ function storeRawMetadata(metadata, raw) { if (!_.isEmpty(metadata)) { raw.metadata = _.cloneDeep(metadata) diff --git a/libraries/overleaf-editor-core/lib/file_data/binary_file_data.js b/libraries/overleaf-editor-core/lib/file_data/binary_file_data.js index e64cd7baff..4e78b359fd 100644 --- a/libraries/overleaf-editor-core/lib/file_data/binary_file_data.js +++ b/libraries/overleaf-editor-core/lib/file_data/binary_file_data.js @@ -5,6 +5,10 @@ const assert = require('check-types').assert const Blob = require('../blob') const FileData = require('./') +/** + * @typedef {import('../types').RawBinaryFileData} RawBinaryFileData + */ + class BinaryFileData extends FileData { /** * @param {string} hash @@ -21,11 +25,18 @@ class BinaryFileData extends FileData { this.byteLength = byteLength } + /** + * @param {RawBinaryFileData} raw + * @returns {BinaryFileData} + */ static fromRaw(raw) { return new BinaryFileData(raw.hash, raw.byteLength) } - /** @inheritdoc */ + /** + * @inheritdoc + * @returns {RawBinaryFileData} + */ toRaw() { return { hash: this.hash, byteLength: this.byteLength } } @@ -60,7 +71,9 @@ class BinaryFileData extends FileData { return FileData.createHollow(this.byteLength, null) } - /** @inheritdoc */ + /** @inheritdoc + * @return {Promise} + */ async store() { return { hash: this.hash } } diff --git a/libraries/overleaf-editor-core/lib/file_data/hash_file_data.js b/libraries/overleaf-editor-core/lib/file_data/hash_file_data.js index 3655baaa41..90ddb55ba0 100644 --- a/libraries/overleaf-editor-core/lib/file_data/hash_file_data.js +++ b/libraries/overleaf-editor-core/lib/file_data/hash_file_data.js @@ -10,6 +10,7 @@ const FileData = require('./') * @typedef {import('./lazy_string_file_data')} LazyStringFileData * @typedef {import('./hollow_string_file_data')} HollowStringFileData * @typedef {import('../types').BlobStore} BlobStore + * @typedef {import('../types').RawHashFileData} RawHashFileData */ class HashFileData extends FileData { @@ -35,8 +36,7 @@ class HashFileData extends FileData { /** * - * @param {{hash: string, rangesHash?: string}} raw - * @returns + * @param {RawHashFileData} raw */ static fromRaw(raw) { return new HashFileData(raw.hash, raw.rangesHash) @@ -44,9 +44,10 @@ class HashFileData extends FileData { /** * @inheritdoc - * @returns {{hash: string, rangesHash?: string}} + * @returns {RawHashFileData} */ toRaw() { + /** @type RawHashFileData */ const raw = { hash: this.hash } if (this.rangesHash) { raw.rangesHash = this.rangesHash @@ -97,6 +98,8 @@ class HashFileData extends FileData { throw new Error('Failed to look up rangesHash in blobStore') } if (!blob) throw new Error('blob not found: ' + this.hash) + // TODO(das7pad): inline 2nd path of FileData.createLazyFromBlobs? + // @ts-ignore return FileData.createLazyFromBlobs(blob, rangesBlob) } @@ -110,14 +113,17 @@ class HashFileData extends FileData { if (!blob) { throw new Error('Failed to look up hash in blobStore') } + // TODO(das7pad): inline 2nd path of FileData.createHollow? + // @ts-ignore return FileData.createHollow(blob.getByteLength(), blob.getStringLength()) } /** * @inheritdoc - * @returns {Promise<{hash: string, rangesHash?: string}>} + * @returns {Promise} */ async store() { + /** @type RawHashFileData */ const raw = { hash: this.hash } if (this.rangesHash) { raw.rangesHash = this.rangesHash diff --git a/libraries/overleaf-editor-core/lib/file_data/hollow_binary_file_data.js b/libraries/overleaf-editor-core/lib/file_data/hollow_binary_file_data.js index 294bdbea4a..93431ebda1 100644 --- a/libraries/overleaf-editor-core/lib/file_data/hollow_binary_file_data.js +++ b/libraries/overleaf-editor-core/lib/file_data/hollow_binary_file_data.js @@ -4,6 +4,10 @@ const assert = require('check-types').assert const FileData = require('./') +/** + * @typedef {import('../types').RawHollowBinaryFileData} RawHollowBinaryFileData + */ + class HollowBinaryFileData extends FileData { /** * @param {number} byteLength @@ -16,11 +20,18 @@ class HollowBinaryFileData extends FileData { this.byteLength = byteLength } + /** + * @param {RawHollowBinaryFileData} raw + * @returns {HollowBinaryFileData} + */ static fromRaw(raw) { return new HollowBinaryFileData(raw.byteLength) } - /** @inheritdoc */ + /** + * @inheritdoc + * @returns {RawHollowBinaryFileData} + */ toRaw() { return { byteLength: this.byteLength } } diff --git a/libraries/overleaf-editor-core/lib/file_data/hollow_string_file_data.js b/libraries/overleaf-editor-core/lib/file_data/hollow_string_file_data.js index 922384795e..a22c07533b 100644 --- a/libraries/overleaf-editor-core/lib/file_data/hollow_string_file_data.js +++ b/libraries/overleaf-editor-core/lib/file_data/hollow_string_file_data.js @@ -8,6 +8,10 @@ const assert = require('check-types').assert const FileData = require('./') +/** + * @typedef {import('../types').RawHollowStringFileData} RawHollowStringFileData + */ + class HollowStringFileData extends FileData { /** * @param {number} stringLength @@ -24,11 +28,18 @@ class HollowStringFileData extends FileData { this.stringLength = stringLength } + /** + * @param {RawHollowStringFileData} raw + * @returns {HollowStringFileData} + */ static fromRaw(raw) { return new HollowStringFileData(raw.stringLength) } - /** @inheritdoc */ + /** + * @inheritdoc + * @returns {RawHollowStringFileData} + */ toRaw() { return { stringLength: this.stringLength } } diff --git a/libraries/overleaf-editor-core/lib/file_data/index.js b/libraries/overleaf-editor-core/lib/file_data/index.js index 792fc802d6..fd5390558b 100644 --- a/libraries/overleaf-editor-core/lib/file_data/index.js +++ b/libraries/overleaf-editor-core/lib/file_data/index.js @@ -6,18 +6,10 @@ const assert = require('check-types').assert const Blob = require('../blob') -// Dependencies are loaded at the bottom of the file to mitigate circular -// dependency -let BinaryFileData = null -let HashFileData = null -let HollowBinaryFileData = null -let HollowStringFileData = null -let LazyStringFileData = null -let StringFileData = null - /** * @typedef {import("../types").BlobStore} BlobStore * @typedef {import("../types").ReadonlyBlobStore} ReadonlyBlobStore + * @typedef {import("../types").RawFileData} RawFileData * @typedef {import("../operation/edit_operation")} EditOperation * @typedef {import("../file_data/comment_list")} CommentList * @typedef {import("../types").CommentRawData} CommentRawData @@ -29,25 +21,37 @@ let StringFileData = null * should be used only through {@link File}. */ class FileData { - /** @see File.fromRaw */ + /** @see File.fromRaw + * @param {RawFileData} raw + */ static fromRaw(raw) { + // TODO(das7pad): can we teach typescript to understand our polymorphism? if (Object.prototype.hasOwnProperty.call(raw, 'hash')) { if (Object.prototype.hasOwnProperty.call(raw, 'byteLength')) + // @ts-ignore return BinaryFileData.fromRaw(raw) if (Object.prototype.hasOwnProperty.call(raw, 'stringLength')) + // @ts-ignore return LazyStringFileData.fromRaw(raw) + // @ts-ignore return HashFileData.fromRaw(raw) } if (Object.prototype.hasOwnProperty.call(raw, 'byteLength')) + // @ts-ignore return HollowBinaryFileData.fromRaw(raw) if (Object.prototype.hasOwnProperty.call(raw, 'stringLength')) + // @ts-ignore return HollowStringFileData.fromRaw(raw) if (Object.prototype.hasOwnProperty.call(raw, 'content')) + // @ts-ignore return StringFileData.fromRaw(raw) throw new Error('FileData: bad raw object ' + JSON.stringify(raw)) } - /** @see File.createHollow */ + /** @see File.createHollow + * @param {number} byteLength + * @param {number|null} stringLength + */ static createHollow(byteLength, stringLength) { if (stringLength == null) { return new HollowBinaryFileData(byteLength) @@ -63,15 +67,25 @@ class FileData { static createLazyFromBlobs(blob, rangesBlob) { assert.instance(blob, Blob, 'FileData: bad blob') if (blob.getStringLength() == null) { - return new BinaryFileData(blob.getHash(), blob.getByteLength()) + return new BinaryFileData( + // TODO(das7pad): see call-sites + // @ts-ignore + blob.getHash(), + blob.getByteLength() + ) } return new LazyStringFileData( + // TODO(das7pad): see call-sites + // @ts-ignore blob.getHash(), rangesBlob?.getHash(), blob.getStringLength() ) } + /** + * @returns {RawFileData} + */ toRaw() { throw new Error('FileData: toRaw not implemented') } @@ -184,7 +198,7 @@ class FileData { * @see File#store * @function * @param {BlobStore} blobStore - * @return {Promise} a raw HashFile + * @return {Promise} a raw HashFile * @abstract */ async store(blobStore) { @@ -216,9 +230,9 @@ class FileData { module.exports = FileData -BinaryFileData = require('./binary_file_data') -HashFileData = require('./hash_file_data') -HollowBinaryFileData = require('./hollow_binary_file_data') -HollowStringFileData = require('./hollow_string_file_data') -LazyStringFileData = require('./lazy_string_file_data') -StringFileData = require('./string_file_data') +const BinaryFileData = require('./binary_file_data') +const HashFileData = require('./hash_file_data') +const HollowBinaryFileData = require('./hollow_binary_file_data') +const HollowStringFileData = require('./hollow_string_file_data') +const LazyStringFileData = require('./lazy_string_file_data') +const StringFileData = require('./string_file_data') diff --git a/libraries/overleaf-editor-core/lib/file_data/lazy_string_file_data.js b/libraries/overleaf-editor-core/lib/file_data/lazy_string_file_data.js index 4d958ac947..fba3e716b0 100644 --- a/libraries/overleaf-editor-core/lib/file_data/lazy_string_file_data.js +++ b/libraries/overleaf-editor-core/lib/file_data/lazy_string_file_data.js @@ -14,6 +14,8 @@ const EditOperationBuilder = require('../operation/edit_operation_builder') * @typedef {import('../types').BlobStore} BlobStore * @typedef {import('../types').ReadonlyBlobStore} ReadonlyBlobStore * @typedef {import('../types').RangesBlob} RangesBlob + * @typedef {import('../types').RawFileData} RawFileData + * @typedef {import('../types').RawLazyStringFileData} RawLazyStringFileData */ class LazyStringFileData extends FileData { @@ -39,6 +41,10 @@ class LazyStringFileData extends FileData { this.operations = operations || [] } + /** + * @param {RawLazyStringFileData} raw + * @returns {LazyStringFileData} + */ static fromRaw(raw) { return new LazyStringFileData( raw.hash, @@ -48,9 +54,16 @@ class LazyStringFileData extends FileData { ) } - /** @inheritdoc */ + /** + * @inheritdoc + * @returns {RawLazyStringFileData} + */ toRaw() { - const raw = { hash: this.hash, stringLength: this.stringLength } + /** @type RawLazyStringFileData */ + const raw = { + hash: this.hash, + stringLength: this.stringLength, + } if (this.rangesHash) { raw.rangesHash = this.rangesHash } @@ -135,18 +148,26 @@ class LazyStringFileData extends FileData { /** @inheritdoc */ async toHollow() { + // TODO(das7pad): inline 2nd path of FileData.createLazyFromBlobs? + // @ts-ignore return FileData.createHollow(null, this.stringLength) } - /** @inheritdoc */ + /** @inheritdoc + * @param {EditOperation} operation + */ edit(operation) { this.stringLength = operation.applyToLength(this.stringLength) this.operations.push(operation) } - /** @inheritdoc */ + /** @inheritdoc + * @param {BlobStore} blobStore + * @return {Promise} + */ async store(blobStore) { if (this.operations.length === 0) { + /** @type RawFileData */ const raw = { hash: this.hash } if (this.rangesHash) { raw.rangesHash = this.rangesHash @@ -155,6 +176,7 @@ class LazyStringFileData extends FileData { } const eager = await this.toEager(blobStore) this.operations.length = 0 + /** @type RawFileData */ return await eager.store(blobStore) } } diff --git a/libraries/overleaf-editor-core/lib/file_data/string_file_data.js b/libraries/overleaf-editor-core/lib/file_data/string_file_data.js index f82b3580a4..6af37c6e74 100644 --- a/libraries/overleaf-editor-core/lib/file_data/string_file_data.js +++ b/libraries/overleaf-editor-core/lib/file_data/string_file_data.js @@ -9,6 +9,7 @@ const TrackedChangeList = require('./tracked_change_list') /** * @typedef {import("../types").StringFileRawData} StringFileRawData + * @typedef {import("../types").RawFileData} RawFileData * @typedef {import("../operation/edit_operation")} EditOperation * @typedef {import("../types").BlobStore} BlobStore * @typedef {import("../types").CommentRawData} CommentRawData @@ -47,6 +48,7 @@ class StringFileData extends FileData { * @returns {StringFileRawData} */ toRaw() { + /** @type StringFileRawData */ const raw = { content: this.content } if (this.comments.length) { @@ -133,6 +135,7 @@ class StringFileData extends FileData { /** * @inheritdoc * @param {BlobStore} blobStore + * @return {Promise} */ async store(blobStore) { const blob = await blobStore.putString(this.content) @@ -143,8 +146,12 @@ class StringFileData extends FileData { trackedChanges: this.trackedChanges.toRaw(), } const rangesBlob = await blobStore.putObject(ranges) + // TODO(das7pad): Provide interface that guarantees hash exists? + // @ts-ignore return { hash: blob.getHash(), rangesHash: rangesBlob.getHash() } } + // TODO(das7pad): Provide interface that guarantees hash exists? + // @ts-ignore return { hash: blob.getHash() } } } diff --git a/libraries/overleaf-editor-core/lib/file_data/tracking_props.js b/libraries/overleaf-editor-core/lib/file_data/tracking_props.js index 5b2f3ae326..cffe7bbef9 100644 --- a/libraries/overleaf-editor-core/lib/file_data/tracking_props.js +++ b/libraries/overleaf-editor-core/lib/file_data/tracking_props.js @@ -1,6 +1,7 @@ // @ts-check /** * @typedef {import("../types").TrackingPropsRawData} TrackingPropsRawData + * @typedef {import("../types").TrackingDirective} TrackingDirective */ class TrackingProps { @@ -48,6 +49,10 @@ class TrackingProps { } } + /** + * @param {TrackingDirective} [other] + * @returns {boolean} + */ equals(other) { if (!(other instanceof TrackingProps)) { return false diff --git a/libraries/overleaf-editor-core/lib/file_map.js b/libraries/overleaf-editor-core/lib/file_map.js index 79939823df..9fb4181f01 100644 --- a/libraries/overleaf-editor-core/lib/file_map.js +++ b/libraries/overleaf-editor-core/lib/file_map.js @@ -9,9 +9,18 @@ const pMap = require('p-map') const File = require('./file') const safePathname = require('./safe_pathname') +/** + * @typedef {import('./types').RawFile} RawFile + * @typedef {import('./types').RawFileMap} RawFileMap + * @typedef {Record} FileMapData + */ + class PathnameError extends OError {} class NonUniquePathnameError extends PathnameError { + /** + * @param {string[]} pathnames + */ constructor(pathnames) { super('pathnames are not unique: ' + pathnames, { pathnames }) this.pathnames = pathnames @@ -19,6 +28,9 @@ class NonUniquePathnameError extends PathnameError { } class BadPathnameError extends PathnameError { + /** + * @param {string} pathname + */ constructor(pathname) { super(pathname + ' is not a valid pathname', { pathname }) this.pathname = pathname @@ -26,6 +38,9 @@ class BadPathnameError extends PathnameError { } class PathnameConflictError extends PathnameError { + /** + * @param {string} pathname + */ constructor(pathname) { super(`pathname '${pathname}' conflicts with another file`, { pathname }) this.pathname = pathname @@ -33,6 +48,9 @@ class PathnameConflictError extends PathnameError { } class FileNotFoundError extends PathnameError { + /** + * @param {string} pathname + */ constructor(pathname) { super(`file ${pathname} does not exist`, { pathname }) this.pathname = pathname @@ -70,13 +88,17 @@ class FileMap { constructor(files) { // create bare object for use as Map // http://ryanmorr.com/true-hash-maps-in-javascript/ - /** @type {Record} */ + /** @type FileMapData */ this.files = Object.create(null) _.assign(this.files, files) checkPathnamesAreUnique(this.files) checkPathnamesDoNotConflict(this) } + /** + * @param {RawFileMap} raw + * @returns {FileMap} + */ static fromRaw(raw) { assert.object(raw, 'bad raw files') return new FileMap(_.mapValues(raw, File.fromRaw)) @@ -85,12 +107,18 @@ class FileMap { /** * Convert to raw object for serialization. * - * @return {Object} + * @return {RawFileMap} */ toRaw() { + /** + * @param {File} file + * @return {RawFile} + */ function fileToRaw(file) { return file.toRaw() } + // TODO(das7pad): refine types to enforce no nulls in FileMapData + // @ts-ignore return _.mapValues(this.files, fileToRaw) } @@ -103,6 +131,8 @@ class FileMap { addFile(pathname, file) { checkPathname(pathname) assert.object(file, 'bad file') + // TODO(das7pad): make ignoredPathname argument fully optional + // @ts-ignore checkNewPathnameDoesNotConflict(this, pathname) addFile(this.files, pathname, file) } @@ -163,7 +193,7 @@ class FileMap { */ getFile(pathname) { const key = findPathnameKey(this.files, pathname) - return key && this.files[key] + if (key) return this.files[key] } /** @@ -177,7 +207,7 @@ class FileMap { * and MoveFile overwrite existing files.) * * @param {string} pathname - * @param {string} [ignoredPathname] pretend this pathname does not exist + * @param {string?} ignoredPathname pretend this pathname does not exist */ wouldConflict(pathname, ignoredPathname) { checkPathname(pathname) @@ -224,7 +254,7 @@ class FileMap { /** * Map the files in this map to new values. * @template T - * @param {(file: File | null) => T} iteratee + * @param {(file: File | null, path: string) => T} iteratee * @return {Record} */ map(iteratee) { @@ -234,9 +264,10 @@ class FileMap { /** * Map the files in this map to new values asynchronously, with an optional * limit on concurrency. - * @param {function} iteratee like for _.mapValues + * @template T + * @param {(file: File | null | undefined, path: string, pathnames: string[]) => T} iteratee * @param {number} [concurrency] - * @return {Promise} + * @return {Promise>} */ async mapAsync(iteratee, concurrency) { assert.maybe.number(concurrency, 'bad concurrency') @@ -253,32 +284,55 @@ class FileMap { } } +/** + * @param {string} pathname0 + * @param {string?} pathname1 + * @returns {boolean} + */ function pathnamesEqual(pathname0, pathname1) { return pathname0 === pathname1 } +/** + * @param {FileMapData} files + * @returns {boolean} + */ function pathnamesAreUnique(files) { const keys = _.keys(files) return _.uniqWith(keys, pathnamesEqual).length === keys.length } +/** + * @param {FileMapData} files + */ function checkPathnamesAreUnique(files) { if (pathnamesAreUnique(files)) return throw new FileMap.NonUniquePathnameError(_.keys(files)) } +/** + * @param {string} pathname + */ function checkPathname(pathname) { assert.nonEmptyString(pathname, 'bad pathname') if (safePathname.isClean(pathname)) return throw new FileMap.BadPathnameError(pathname) } +/** + * @param {FileMap} fileMap + * @param {string} pathname + * @param {string?} ignoredPathname + */ function checkNewPathnameDoesNotConflict(fileMap, pathname, ignoredPathname) { if (fileMap.wouldConflict(pathname, ignoredPathname)) { throw new FileMap.PathnameConflictError(pathname) } } +/** + * @param {FileMap} fileMap + */ function checkPathnamesDoNotConflict(fileMap) { const pathnames = fileMap.getPathnames() // check pathnames for validity first @@ -299,18 +353,29 @@ function checkPathnamesDoNotConflict(fileMap) { } } -// -// This function is somewhat vestigial: it was used when this map used -// case-insensitive pathname comparison. We could probably simplify some of the -// logic in the callers, but in the hope that we will one day return to -// case-insensitive semantics, we've just left things as-is for now. -// +/** + * This function is somewhat vestigial: it was used when this map used + * case-insensitive pathname comparison. We could probably simplify some of the + * logic in the callers, but in the hope that we will one day return to + * case-insensitive semantics, we've just left things as-is for now. + * + * TODO(das7pad): In a followup, inline this function and make types stricter. + * + * @param {FileMapData} files + * @param {string} pathname + * @returns {string | undefined} + */ function findPathnameKey(files, pathname) { // we can check for the key without worrying about properties // in the prototype because we are now using a bare object/ if (pathname in files) return pathname } +/** + * @param {FileMapData} files + * @param {string} pathname + * @param {File?} file + */ function addFile(files, pathname, file) { const key = findPathnameKey(files, pathname) if (key) delete files[key] diff --git a/libraries/overleaf-editor-core/lib/label.js b/libraries/overleaf-editor-core/lib/label.js index 8ec86e0a01..8f7345d93c 100644 --- a/libraries/overleaf-editor-core/lib/label.js +++ b/libraries/overleaf-editor-core/lib/label.js @@ -1,7 +1,12 @@ +// @ts-check 'use strict' const assert = require('check-types').assert +/** + * @typedef {import('./types').RawLabel} RawLabel + */ + /** * @classdesc * A user-configurable label that can be attached to a specific change. Labels @@ -13,6 +18,9 @@ class Label { /** * @constructor * @param {string} text + * @param {number?} authorId + * @param {Date} timestamp + * @param {number} version */ constructor(text, authorId, timestamp, version) { assert.string(text, 'bad text') @@ -29,7 +37,7 @@ class Label { /** * Create a Label from its raw form. * - * @param {Object} raw + * @param {RawLabel} raw * @return {Label} */ static fromRaw(raw) { @@ -44,7 +52,7 @@ class Label { /** * Convert the Label to raw form for transmission. * - * @return {Object} + * @return {RawLabel} */ toRaw() { return { @@ -81,7 +89,7 @@ class Label { } /** - * @return {number | undefined} + * @return {number} */ getVersion() { return this.version diff --git a/libraries/overleaf-editor-core/lib/operation/add_comment_operation.js b/libraries/overleaf-editor-core/lib/operation/add_comment_operation.js index 2b08d02572..efadb501ed 100644 --- a/libraries/overleaf-editor-core/lib/operation/add_comment_operation.js +++ b/libraries/overleaf-editor-core/lib/operation/add_comment_operation.js @@ -44,6 +44,7 @@ class AddCommentOperation extends EditOperation { * @returns {RawAddCommentOperation} */ toJSON() { + /** @type RawAddCommentOperation */ const raw = { commentId: this.commentId, ranges: this.ranges.map(range => range.toRaw()), diff --git a/libraries/overleaf-editor-core/lib/operation/edit_file_operation.js b/libraries/overleaf-editor-core/lib/operation/edit_file_operation.js index 006be4dc13..1e014b21fe 100644 --- a/libraries/overleaf-editor-core/lib/operation/edit_file_operation.js +++ b/libraries/overleaf-editor-core/lib/operation/edit_file_operation.js @@ -1,6 +1,10 @@ // @ts-check 'use strict' -/** @typedef {import('./edit_operation')} EditOperation */ +/** + * @typedef {import('./edit_operation')} EditOperation + * @typedef {import('../types').RawEditFileOperation} RawEditFileOperation + * @typedef {import("../snapshot")} Snapshot + */ const Operation = require('./') const EditOperationBuilder = require('./edit_operation_builder') @@ -32,7 +36,7 @@ class EditFileOperation extends Operation { /** * Deserialize an EditFileOperation. * - * @param {Object} raw + * @param {RawEditFileOperation} raw * @return {EditFileOperation} */ static fromRaw(raw) { @@ -52,13 +56,18 @@ class EditFileOperation extends Operation { /** * @inheritdoc + * @param {Snapshot} snapshot */ applyTo(snapshot) { + // TODO(das7pad): can we teach typescript our polymorphism? + // @ts-ignore snapshot.editFile(this.pathname, this.operation) } /** * @inheritdoc + * @param {Operation} other + * @return {boolean} */ canBeComposedWithForUndo(other) { return ( @@ -69,6 +78,8 @@ class EditFileOperation extends Operation { /** * @inheritdoc + * @param {Operation} other + * @return {other is EditFileOperation} */ canBeComposedWith(other) { // Ensure that other operation is an edit file operation @@ -81,6 +92,7 @@ class EditFileOperation extends Operation { /** * @inheritdoc + * @param {EditFileOperation} other */ compose(other) { return new EditFileOperation( diff --git a/libraries/overleaf-editor-core/lib/operation/edit_no_operation.js b/libraries/overleaf-editor-core/lib/operation/edit_no_operation.js index bd14db868b..5201b8f6df 100644 --- a/libraries/overleaf-editor-core/lib/operation/edit_no_operation.js +++ b/libraries/overleaf-editor-core/lib/operation/edit_no_operation.js @@ -1,5 +1,9 @@ const EditOperation = require('./edit_operation') +/** + * @typedef {import('../types').RawEditNoOperation} RawEditNoOperation + */ + class EditNoOperation extends EditOperation { /** * @inheritdoc @@ -9,7 +13,7 @@ class EditNoOperation extends EditOperation { /** * @inheritdoc - * @returns {object} + * @returns {RawEditNoOperation} */ toJSON() { return { diff --git a/libraries/overleaf-editor-core/lib/operation/edit_operation.js b/libraries/overleaf-editor-core/lib/operation/edit_operation.js index a4de31f112..2617ae6ad4 100644 --- a/libraries/overleaf-editor-core/lib/operation/edit_operation.js +++ b/libraries/overleaf-editor-core/lib/operation/edit_operation.js @@ -1,6 +1,7 @@ // @ts-check /** * @typedef {import('../file_data')} FileData + * @typedef {import('../types').RawEditOperation} RawEditOperation */ class EditOperation { @@ -12,7 +13,7 @@ class EditOperation { /** * Converts operation into a JSON value. - * @returns {object} + * @returns {RawEditOperation} */ toJSON() { throw new Error('Abstract method not implemented') diff --git a/libraries/overleaf-editor-core/lib/operation/scan_op.js b/libraries/overleaf-editor-core/lib/operation/scan_op.js index fd27d55662..9600c38c21 100644 --- a/libraries/overleaf-editor-core/lib/operation/scan_op.js +++ b/libraries/overleaf-editor-core/lib/operation/scan_op.js @@ -142,7 +142,9 @@ class InsertOp extends ScanOp { return current } - /** @inheritdoc */ + /** @inheritdoc + * @param {ScanOp} other + */ equals(other) { if (!(other instanceof InsertOp)) { return false @@ -167,6 +169,10 @@ class InsertOp extends ScanOp { return !other.commentIds } + /** + * @param {ScanOp} other + * @return {other is InsertOp} + */ canMergeWith(other) { if (!(other instanceof InsertOp)) { return false @@ -187,6 +193,9 @@ class InsertOp extends ScanOp { return !other.commentIds } + /** + * @param {ScanOp} other + */ mergeWith(other) { if (!this.canMergeWith(other)) { throw new Error('Cannot merge with incompatible operation') @@ -202,6 +211,7 @@ class InsertOp extends ScanOp { if (!this.tracking && !this.commentIds) { return this.insertion } + /** @type RawInsertOp */ const obj = { i: this.insertion } if (this.tracking) { obj.tracking = this.tracking.toRaw() @@ -274,7 +284,9 @@ class RetainOp extends ScanOp { return new RetainOp(op.r) } - /** @inheritdoc */ + /** @inheritdoc + * @param {ScanOp} other + */ equals(other) { if (!(other instanceof RetainOp)) { return false @@ -288,6 +300,10 @@ class RetainOp extends ScanOp { return !other.tracking } + /** + * @param {ScanOp} other + * @return {other is RetainOp} + */ canMergeWith(other) { if (!(other instanceof RetainOp)) { return false @@ -298,6 +314,9 @@ class RetainOp extends ScanOp { return !other.tracking } + /** + * @param {ScanOp} other + */ mergeWith(other) { if (!this.canMergeWith(other)) { throw new Error('Cannot merge with incompatible operation') @@ -321,6 +340,9 @@ class RetainOp extends ScanOp { } class RemoveOp extends ScanOp { + /** + * @param {number} length + */ constructor(length) { super() if (length < 0) { @@ -352,7 +374,11 @@ class RemoveOp extends ScanOp { return new RemoveOp(-op) } - /** @inheritdoc */ + /** + * @inheritdoc + * @param {ScanOp} other + * @return {boolean} + */ equals(other) { if (!(other instanceof RemoveOp)) { return false @@ -360,10 +386,17 @@ class RemoveOp extends ScanOp { return this.length === other.length } + /** + * @param {ScanOp} other + * @return {other is RemoveOp} + */ canMergeWith(other) { return other instanceof RemoveOp } + /** + * @param {ScanOp} other + */ mergeWith(other) { if (!this.canMergeWith(other)) { throw new Error('Cannot merge with incompatible operation') diff --git a/libraries/overleaf-editor-core/lib/operation/set_comment_state_operation.js b/libraries/overleaf-editor-core/lib/operation/set_comment_state_operation.js index 5bc1ec48c6..25c1ffa224 100644 --- a/libraries/overleaf-editor-core/lib/operation/set_comment_state_operation.js +++ b/libraries/overleaf-editor-core/lib/operation/set_comment_state_operation.js @@ -48,7 +48,7 @@ class SetCommentStateOperation extends EditOperation { } /** - * + * @param {StringFileData} previousState * @returns {SetCommentStateOperation | EditNoOperation} */ invert(previousState) { @@ -77,7 +77,7 @@ class SetCommentStateOperation extends EditOperation { /** * @inheritdoc * @param {EditOperation} other - * @returns {EditOperation} + * @returns {SetCommentStateOperation | core.DeleteCommentOperation} */ compose(other) { if ( diff --git a/libraries/overleaf-editor-core/lib/operation/text_operation.js b/libraries/overleaf-editor-core/lib/operation/text_operation.js index 25f297bf1f..29690ce74e 100644 --- a/libraries/overleaf-editor-core/lib/operation/text_operation.js +++ b/libraries/overleaf-editor-core/lib/operation/text_operation.js @@ -35,6 +35,7 @@ const TrackingProps = require('../file_data/tracking_props') * @typedef {import('../operation/scan_op').ScanOp} ScanOp * @typedef {import('../file_data/tracked_change_list')} TrackedChangeList * @typedef {import('../types').TrackingDirective} TrackingDirective + * @typedef {{tracking?: TrackingProps, commentIds?: string[]}} InsertOptions */ /** @@ -69,6 +70,10 @@ class TextOperation extends EditOperation { this.targetLength = 0 } + /** + * @param {TextOperation} other + * @return {boolean} + */ equals(other) { if (this.baseLength !== other.baseLength) { return false @@ -129,7 +134,7 @@ class TextOperation extends EditOperation { /** * Insert a string at the current position. * @param {string | {i: string}} insertValue - * @param {{tracking?: TrackingProps, commentIds?: string[]}} opts + * @param {InsertOptions} opts * @returns {TextOperation} */ insert(insertValue, opts = {}) { @@ -328,6 +333,8 @@ class TextOperation extends EditOperation { /** * @inheritdoc + * @param {number} length of the original string; non-negative + * @return {number} length of the new string; non-negative */ applyToLength(length) { const operation = this @@ -573,6 +580,7 @@ class TextOperation extends EditOperation { op1 = ops1[i1++] } } else if (op1 instanceof InsertOp && op2 instanceof RetainOp) { + /** @type InsertOptions */ const opts = { commentIds: op1.commentIds, } @@ -807,6 +815,10 @@ function getSimpleOp(operation) { return null } +/** + * @param {TextOperation} operation + * @return {number} + */ function getStartIndex(operation) { if (operation.ops[0] instanceof RetainOp) { return operation.ops[0].length @@ -843,7 +855,10 @@ function calculateTrackingCommentSegments( const breaks = new Set() const opStart = cursor const opEnd = cursor + length - // Utility function to limit breaks to the boundary set by the operation range + /** + * Utility function to limit breaks to the boundary set by the operation range + * @param {number} rangeBoundary + */ function addBreak(rangeBoundary) { if (rangeBoundary < opStart || rangeBoundary > opEnd) { return diff --git a/libraries/overleaf-editor-core/lib/range.js b/libraries/overleaf-editor-core/lib/range.js index 7a92f55ec9..1847c2a273 100644 --- a/libraries/overleaf-editor-core/lib/range.js +++ b/libraries/overleaf-editor-core/lib/range.js @@ -2,6 +2,10 @@ const OError = require('@overleaf/o-error') +/** + * @typedef {import('./types').RawRange} RawRange + */ + class Range { /** * @param {number} pos @@ -17,10 +21,16 @@ class Range { this.length = length } + /** + * @return {number} + */ get start() { return this.pos } + /** + * @return {number} + */ get end() { return this.pos + this.length } @@ -193,6 +203,10 @@ class Range { } } + /** + * @param {RawRange} raw + * @return {Range} + */ static fromRaw(raw) { return new Range(raw.pos, raw.length) } diff --git a/libraries/overleaf-editor-core/lib/safe_pathname.js b/libraries/overleaf-editor-core/lib/safe_pathname.js index 04deafdb29..1bd5b6c8b2 100644 --- a/libraries/overleaf-editor-core/lib/safe_pathname.js +++ b/libraries/overleaf-editor-core/lib/safe_pathname.js @@ -1,4 +1,4 @@ -/** @module */ +// @ts-check 'use strict' const path = require('path-browserify') @@ -39,6 +39,7 @@ const MAX_PATH = 1024 /** * Replace invalid characters and filename patterns in a filename with * underscores. + * @param {string} filename */ function cleanPart(filename) { filename = filename.replace(BAD_CHAR_RX, '_') diff --git a/libraries/overleaf-editor-core/lib/snapshot.js b/libraries/overleaf-editor-core/lib/snapshot.js index 779fdbdbf8..e880f68558 100644 --- a/libraries/overleaf-editor-core/lib/snapshot.js +++ b/libraries/overleaf-editor-core/lib/snapshot.js @@ -1,3 +1,4 @@ +// @ts-check 'use strict' const assert = require('check-types').assert @@ -10,9 +11,11 @@ const FILE_LOAD_CONCURRENCY = 50 /** * @typedef {import("./types").BlobStore} BlobStore + * @typedef {import("./types").RawSnapshot} RawSnapshot * @typedef {import("./types").ReadonlyBlobStore} ReadonlyBlobStore * @typedef {import("./change")} Change * @typedef {import("./operation/text_operation")} TextOperation + * @typedef {import("./file")} File */ class EditMissingFileError extends OError {} @@ -26,6 +29,10 @@ class Snapshot { static PROJECT_VERSION_RX = new RegExp(Snapshot.PROJECT_VERSION_RX_STRING) static EditMissingFileError = EditMissingFileError + /** + * @param {RawSnapshot} raw + * @return {Snapshot} + */ static fromRaw(raw) { assert.object(raw.files, 'bad raw.files') return new Snapshot( @@ -36,6 +43,7 @@ class Snapshot { } toRaw() { + /** @type RawSnapshot */ const raw = { files: this.fileMap.toRaw(), } @@ -64,6 +72,9 @@ class Snapshot { return this.projectVersion } + /** + * @param {string} projectVersion + */ setProjectVersion(projectVersion) { assert.maybe.match( projectVersion, @@ -80,6 +91,9 @@ class Snapshot { return this.v2DocVersions } + /** + * @param {V2DocVersions} v2DocVersions + */ setV2DocVersions(v2DocVersions) { assert.maybe.instance( v2DocVersions, @@ -89,6 +103,9 @@ class Snapshot { this.v2DocVersions = v2DocVersions } + /** + * @param {V2DocVersions} v2DocVersions + */ updateV2DocVersions(v2DocVersions) { // merge new v2DocVersions into this.v2DocVersions v2DocVersions.applyTo(this) @@ -114,6 +131,7 @@ class Snapshot { /** * Get a File by its pathname. * @see FileMap#getFile + * @param {string} pathname */ getFile(pathname) { return this.fileMap.getFile(pathname) @@ -122,6 +140,8 @@ class Snapshot { /** * Add the given file to the snapshot. * @see FileMap#addFile + * @param {string} pathname + * @param {File} file */ addFile(pathname, file) { this.fileMap.addFile(pathname, file) @@ -130,6 +150,8 @@ class Snapshot { /** * Move or remove a file. * @see FileMap#moveFile + * @param {string} pathname + * @param {string} newPathname */ moveFile(pathname, newPathname) { this.fileMap.moveFile(pathname, newPathname) @@ -185,13 +207,18 @@ class Snapshot { * @param {Set.} blobHashes */ findBlobHashes(blobHashes) { - // eslint-disable-next-line array-callback-return - this.fileMap.map(file => { + /** + * @param {File} file + */ + function find(file) { const hash = file.getHash() const rangeHash = file.getRangesHash() if (hash) blobHashes.add(hash) if (rangeHash) blobHashes.add(rangeHash) - }) + } + // TODO(das7pad): refine types to enforce no nulls in FileMapData + // @ts-ignore + this.fileMap.map(find) } /** @@ -203,10 +230,15 @@ class Snapshot { * values are the files in the snapshot */ async loadFiles(kind, blobStore) { - return await this.fileMap.mapAsync( - file => file.load(kind, blobStore), - FILE_LOAD_CONCURRENCY - ) + /** + * @param {File} file + */ + function load(file) { + return file.load(kind, blobStore) + } + // TODO(das7pad): refine types to enforce no nulls in FileMapData + // @ts-ignore + return await this.fileMap.mapAsync(load, FILE_LOAD_CONCURRENCY) } /** @@ -224,10 +256,16 @@ class Snapshot { const rawV2DocVersions = this.v2DocVersions ? this.v2DocVersions.toRaw() : undefined - const rawFiles = await this.fileMap.mapAsync( - file => file.store(blobStore), - concurrency - ) + + /** + * @param {File} file + */ + function store(file) { + return file.store(blobStore) + } + // TODO(das7pad): refine types to enforce no nulls in FileMapData + // @ts-ignore + const rawFiles = await this.fileMap.mapAsync(store, concurrency) return { files: rawFiles, projectVersion, diff --git a/libraries/overleaf-editor-core/lib/types.ts b/libraries/overleaf-editor-core/lib/types.ts index 03905055e7..de50f241cf 100644 --- a/libraries/overleaf-editor-core/lib/types.ts +++ b/libraries/overleaf-editor-core/lib/types.ts @@ -17,19 +17,19 @@ export type RangesBlob = { trackedChanges: TrackedChangeRawData[] } -type Range = { +export type RawRange = { pos: number length: number } export type CommentRawData = { id: string - ranges: Range[] + ranges: RawRange[] resolved?: boolean } export type TrackedChangeRawData = { - range: Range + range: RawRange tracking: TrackingPropsRawData } @@ -51,6 +51,35 @@ export type StringFileRawData = { trackedChanges?: TrackedChangeRawData[] } +export type RawSnapshot = { + files: RawFileMap + projectVersion?: string + v2DocVersions?: RawV2DocVersions | null +} + +export type RawFileMap = Record + +export type RawFile = { metadata?: Object } & RawFileData + +export type RawFileData = + | RawBinaryFileData + | RawHashFileData + | RawHollowBinaryFileData + | RawHollowStringFileData + | RawLazyStringFileData + | StringFileRawData + +export type RawHashFileData = { hash: string; rangesHash?: string } +export type RawBinaryFileData = { hash: string; byteLength: number } +export type RawLazyStringFileData = { + hash: string + stringLength: number + rangesHash?: string + operations?: RawEditOperation[] +} +export type RawHollowBinaryFileData = { byteLength: number } +export type RawHollowStringFileData = { stringLength: number } + export type RawV2DocVersions = Record export type RawInsertOp = @@ -78,7 +107,7 @@ export type RawTextOperation = { export type RawAddCommentOperation = { commentId: string - ranges: Range[] + ranges: RawRange[] resolved?: boolean } @@ -89,14 +118,28 @@ export type RawSetCommentStateOperation = { resolved: boolean } +export type RawEditNoOperation = { + noOp: true +} + +export type RawEditFileOperation = RawEditOperation & { pathname: string } + export type RawEditOperation = | RawTextOperation | RawAddCommentOperation | RawDeleteCommentOperation | RawSetCommentStateOperation + | RawEditNoOperation export type LinkedFileData = { importedAt: string provider: string [other: string]: any } + +export type RawLabel = { + text: string + authorId: number | null + timestamp: string + version: number +} diff --git a/libraries/overleaf-editor-core/lib/v2_doc_versions.js b/libraries/overleaf-editor-core/lib/v2_doc_versions.js index 4af306babd..711b8457ce 100644 --- a/libraries/overleaf-editor-core/lib/v2_doc_versions.js +++ b/libraries/overleaf-editor-core/lib/v2_doc_versions.js @@ -1,9 +1,11 @@ +// @ts-check 'use strict' const _ = require('lodash') /** * @typedef {import("./file")} File + * @typedef {import("./snapshot")} Snapshot * @typedef {import("./types").RawV2DocVersions} RawV2DocVersions */ @@ -15,13 +17,17 @@ class V2DocVersions { this.data = data || {} } + /** + * @param {RawV2DocVersions?} [raw] + * @return {V2DocVersions|undefined} + */ static fromRaw(raw) { if (!raw) return undefined return new V2DocVersions(raw) } /** - * @abstract + * @return {?RawV2DocVersions} */ toRaw() { if (!this.data) return null @@ -32,12 +38,15 @@ class V2DocVersions { /** * Clone this object. * - * @return {V2DocVersions} a new object of the same type + * @return {V2DocVersions|undefined} a new object of the same type */ clone() { return V2DocVersions.fromRaw(this.toRaw()) } + /** + * @param {Snapshot} snapshot + */ applyTo(snapshot) { // Only update the snapshot versions if we have new versions if (!_.size(this.data)) return @@ -54,6 +63,8 @@ class V2DocVersions { /** * Move or remove a doc. * Must be called after FileMap#moveFile, which validates the paths. + * @param {string} pathname + * @param {string} newPathname */ moveFile(pathname, newPathname) { for (const [id, v] of Object.entries(this.data)) { diff --git a/libraries/overleaf-editor-core/package.json b/libraries/overleaf-editor-core/package.json index 8dbf72952f..bc0cdebbf7 100644 --- a/libraries/overleaf-editor-core/package.json +++ b/libraries/overleaf-editor-core/package.json @@ -5,10 +5,10 @@ "main": "index.js", "scripts": { "test": "npm run lint && npm run format && npm run types:check && npm run test:unit", - "format": "prettier --list-different $PWD/'**/*.{js,cjs}'", - "format:fix": "prettier --write $PWD/'**/*.{js,cjs}'", - "lint": "eslint --ext .js --ext .cjs --max-warnings 0 --format unix .", - "lint:fix": "eslint --fix --ext .js --ext .cjs .", + "format": "prettier --list-different $PWD/'**/*.{js,cjs,ts}'", + "format:fix": "prettier --write $PWD/'**/*.{js,cjs,ts}'", + "lint": "eslint --ext .js --ext .cjs --ext .ts --max-warnings 0 --format unix .", + "lint:fix": "eslint --fix --ext .js --ext .cjs --ext .ts .", "test:ci": "npm run test:unit", "test:unit": "mocha --exit test/**/*.{js,cjs}", "types:check": "tsc --noEmit" @@ -17,6 +17,8 @@ "license": "Proprietary", "private": true, "devDependencies": { + "@types/check-types": "^7.3.7", + "@types/path-browserify": "^1.0.2", "chai": "^3.3.0", "mocha": "^10.2.0", "sinon": "^9.2.4", diff --git a/package-lock.json b/package-lock.json index 2a7454a1bf..60913b8770 100644 --- a/package-lock.json +++ b/package-lock.json @@ -397,6 +397,8 @@ "path-browserify": "^1.0.1" }, "devDependencies": { + "@types/check-types": "^7.3.7", + "@types/path-browserify": "^1.0.2", "chai": "^3.3.0", "mocha": "^10.2.0", "sinon": "^9.2.4", @@ -12532,6 +12534,12 @@ "@types/chai": "*" } }, + "node_modules/@types/check-types": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@types/check-types/-/check-types-7.3.7.tgz", + "integrity": "sha512-ZNAGaVc/joAV3lAuRwPdsQY/caU1RvKoa+U7i/TkYIlOStdYq4vyArFnA1zItfEDkHpXNWApWIqqbp5fsHAiRg==", + "dev": true + }, "node_modules/@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -13015,6 +13023,12 @@ "@types/passport": "*" } }, + "node_modules/@types/path-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/path-browserify/-/path-browserify-1.0.2.tgz", + "integrity": "sha512-ZkC5IUqqIFPXx3ASTTybTzmQdwHwe2C0u3eL75ldQ6T9E9IWFJodn6hIfbZGab73DfyiHN4Xw15gNxUq2FbvBA==", + "dev": true + }, "node_modules/@types/pg": { "version": "8.6.1", "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", @@ -44847,6 +44861,7 @@ "mock-fs": "^5.1.2", "node-fetch": "^2.6.7", "nvd3": "^1.8.6", + "overleaf-editor-core": "*", "pdfjs-dist213": "npm:pdfjs-dist@2.13.216", "pdfjs-dist401": "npm:pdfjs-dist@4.5.136", "pirates": "^4.0.1", @@ -53363,6 +53378,7 @@ "nvd3": "^1.8.6", "on-headers": "^1.0.2", "otplib": "^12.0.1", + "overleaf-editor-core": "*", "p-limit": "^2.3.0", "p-props": "4.0.0", "parse-data-url": "^2.0.0", @@ -57422,6 +57438,12 @@ "@types/chai": "*" } }, + "@types/check-types": { + "version": "7.3.7", + "resolved": "https://registry.npmjs.org/@types/check-types/-/check-types-7.3.7.tgz", + "integrity": "sha512-ZNAGaVc/joAV3lAuRwPdsQY/caU1RvKoa+U7i/TkYIlOStdYq4vyArFnA1zItfEDkHpXNWApWIqqbp5fsHAiRg==", + "dev": true + }, "@types/connect": { "version": "3.4.35", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", @@ -57904,6 +57926,12 @@ "@types/passport": "*" } }, + "@types/path-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@types/path-browserify/-/path-browserify-1.0.2.tgz", + "integrity": "sha512-ZkC5IUqqIFPXx3ASTTybTzmQdwHwe2C0u3eL75ldQ6T9E9IWFJodn6hIfbZGab73DfyiHN4Xw15gNxUq2FbvBA==", + "dev": true + }, "@types/pg": { "version": "8.6.1", "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", @@ -72798,6 +72826,8 @@ "version": "file:libraries/overleaf-editor-core", "requires": { "@overleaf/o-error": "*", + "@types/check-types": "^7.3.7", + "@types/path-browserify": "^1.0.2", "chai": "^3.3.0", "check-types": "^5.1.0", "lodash": "^4.17.19", diff --git a/services/history-v1/storage/lib/blob_store/index.js b/services/history-v1/storage/lib/blob_store/index.js index 812616e51a..830120e3ea 100644 --- a/services/history-v1/storage/lib/blob_store/index.js +++ b/services/history-v1/storage/lib/blob_store/index.js @@ -296,7 +296,7 @@ class BlobStore { * Read a blob metadata record by hexadecimal hash. * * @param {string} hash hexadecimal SHA-1 hash - * @return {Promise.} + * @return {Promise} */ async getBlob(hash) { assert.blobHash(hash, 'bad hash') diff --git a/services/web/frontend/js/features/full-project-on-client/import-overleaf-editor-core-for-type-check.ts b/services/web/frontend/js/features/full-project-on-client/import-overleaf-editor-core-for-type-check.ts new file mode 100644 index 0000000000..5d9eaa9036 --- /dev/null +++ b/services/web/frontend/js/features/full-project-on-client/import-overleaf-editor-core-for-type-check.ts @@ -0,0 +1 @@ +import 'overleaf-editor-core' diff --git a/services/web/package.json b/services/web/package.json index 9687e5c6bf..e984fd4f06 100644 --- a/services/web/package.json +++ b/services/web/package.json @@ -320,6 +320,7 @@ "mock-fs": "^5.1.2", "node-fetch": "^2.6.7", "nvd3": "^1.8.6", + "overleaf-editor-core": "*", "pdfjs-dist213": "npm:pdfjs-dist@2.13.216", "pdfjs-dist401": "npm:pdfjs-dist@4.5.136", "pirates": "^4.0.1",