Skip to content

Commit 2a895ba

Browse files
committed
Refactoring rollup plugin to extract the contents of the database module declaration in global_index.d.ts. This is responding to feedback from the peer review about location and documentation of this plugin
1 parent 20c7bdb commit 2a895ba

File tree

3 files changed

+151
-31
lines changed

3 files changed

+151
-31
lines changed

packages/firestore/rollup.config.js

Lines changed: 10 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import tmp from 'tmp';
2626
import typescript from 'typescript';
2727

2828
import { generateBuildTargetReplaceConfig } from '../../scripts/build/rollup_replace_build_target';
29+
import { replaceDeclareModule } from '../../scripts/build/rollup_replace_declare_module';
2930

3031
import pkg from './package.json';
3132
import tsconfig from './tsconfig.json';
@@ -59,35 +60,6 @@ const browserPlugins = [
5960
terser(util.manglePrivatePropertiesOptions)
6061
];
6162

62-
// TODO - update the implementation to match all content in the declare module block.
63-
function declareModuleReplacePlugin() {
64-
// The regex we created earlier
65-
const moduleToReplace =
66-
/declare module '\.\/\S+' \{\s+interface Firestore \{\s+pipeline\(\): PipelineSource<Pipeline>;\s+}\s*}/gm;
67-
68-
// What to replace it with (an empty string to remove it)
69-
const replacement =
70-
'interface Firestore {pipeline(): PipelineSource<Pipeline>;}';
71-
72-
return {
73-
name: 'declare-module-replace',
74-
generateBundle(options, bundle) {
75-
const outputFileName = 'global_index.d.ts';
76-
if (!bundle[outputFileName]) {
77-
console.warn(
78-
`[regexReplacePlugin] File not found in bundle: ${outputFileName}`
79-
);
80-
return;
81-
}
82-
83-
const chunk = bundle[outputFileName];
84-
if (chunk.type === 'chunk') {
85-
chunk.code = chunk.code.replace(moduleToReplace, replacement);
86-
}
87-
}
88-
};
89-
}
90-
9163
const allBuilds = [
9264
// Intermediate Node ESM build without build target reporting
9365
// this is an intermediate build used to generate the actual esm and cjs builds
@@ -253,7 +225,15 @@ const allBuilds = [
253225
respectExternal: true
254226
}),
255227

256-
declareModuleReplacePlugin()
228+
// The global.d.ts input file will include
229+
// a `declare module './database' { ... }` block. This block
230+
// was not removed in the build, and the module
231+
// './database' is not known in context of the global.d.ts file.
232+
// Use the declareModuleReplacePlugin to replace:
233+
// `declare module './database' { Y }`
234+
// with the contents of the block:
235+
// `Y`
236+
replaceDeclareModule('global_index.d.ts', './database')
257237
]
258238
}
259239
];

packages/firestore/src/core/firestore_client.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,6 @@ export class FirestoreClient {
147147
*/
148148
public asyncQueue: AsyncQueue,
149149
/**
150-
* @internal
151150
* Exposed for testing
152151
*/
153152
public _databaseInfo: DatabaseInfo,
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
* @license
3+
* Copyright 2025 Google LLC
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
/**
19+
* Returns a rollup plugin to replace any `declare module X { Y }` code blocks with `Y`.
20+
* This was developed to support the generation of `global_index.d.ts`
21+
* used for the Google3 import of `firebase/firestore`
22+
*
23+
* @param fileName perform the replace in this file
24+
* @param moduleName search for and replace this module declaration
25+
*/
26+
export function replaceDeclareModule(fileName, moduleName) {
27+
return {
28+
name: 'replace-declare-module',
29+
generateBundle(options, bundle) {
30+
if (!bundle[fileName]) {
31+
console.warn(
32+
`[replace-declare-module] File not found in bundle: ${fileName}`
33+
);
34+
return;
35+
}
36+
37+
const chunk = bundle[fileName];
38+
if (chunk.type === 'chunk') {
39+
do {
40+
const originalString = chunk.code;
41+
const blockInfo = findDeclareModuleBlock(originalString, moduleName);
42+
const fullBlock = blockInfo?.fullBlock;
43+
const innerContent = blockInfo?.innerContent;
44+
45+
if (!fullBlock || !innerContent) break;
46+
47+
// 1. Get the segment of the string BEFORE the full block starts.
48+
const beforeBlock = originalString.substring(0, fullBlock.start);
49+
50+
// 2. Extract the inner content string based on its start and length.
51+
// We use innerContent indices here to get the actual content inside the braces.
52+
const innerContentString = originalString.substring(
53+
innerContent.start,
54+
innerContent.start + innerContent.length
55+
);
56+
57+
// 3. Get the segment of the string AFTER the full block ends.
58+
// The start index of the 'after' segment is the end index of the full block.
59+
const afterBlockStart = fullBlock.start + fullBlock.length;
60+
const afterBlock = originalString.substring(afterBlockStart);
61+
62+
// 4. Concatenate the three parts: Before + Inner Content + After
63+
chunk.code = beforeBlock + innerContentString + afterBlock;
64+
} while (true);
65+
}
66+
}
67+
};
68+
}
69+
70+
/**
71+
* Searches a multi-line string for a TypeScript module declaration pattern,
72+
* finds the matching closing brace while respecting nested braces, and
73+
* returns the start and length information for the full block and its inner content.
74+
*
75+
* @param inputString The multi-line string content to search within.
76+
* @param moduleName The module name of the declare module block to search for.
77+
* @returns An object containing the BlockInfo for the full block and inner content, or null if not found.
78+
*/
79+
function findDeclareModuleBlock(inputString, moduleName) {
80+
// We escape potential regex characters in the module name to ensure it matches literally.
81+
const escapedModuleName = moduleName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
82+
83+
// Construct the RegExp object dynamically. It searches for:
84+
// 'declare module ' + single quote + escaped module name + single quote + space + '{'
85+
const searchRegex = new RegExp(`declare module '${escapedModuleName}' \{`);
86+
const match = inputString.match(searchRegex);
87+
88+
if (!match || match.index === undefined) {
89+
console.log('No matching module declaration found.');
90+
return { fullBlock: null, innerContent: null };
91+
}
92+
93+
const fullBlockStartIndex = match.index;
94+
95+
// 2. Determine the exact index of the opening brace '{'
96+
// The match[0] gives the text that matched the regex, e.g., "declare module './my-module' {"
97+
const matchText = match[0];
98+
const openBraceOffset = matchText.lastIndexOf('{');
99+
const openBraceIndex = fullBlockStartIndex + openBraceOffset;
100+
101+
let braceCount = 1;
102+
let closeBraceIndex = -1;
103+
104+
// 3. Iterate from the character *after* the opening brace to find the matching '}'
105+
for (let i = openBraceIndex + 1; i < inputString.length; i++) {
106+
const char = inputString[i];
107+
108+
if (char === '{') {
109+
braceCount++;
110+
} else if (char === '}') {
111+
braceCount--;
112+
}
113+
114+
// 4. Check if we found the outer closing brace
115+
if (braceCount === 0) {
116+
closeBraceIndex = i;
117+
break;
118+
}
119+
}
120+
121+
if (closeBraceIndex === -1) {
122+
console.log('Found opening brace but no matching closing brace.');
123+
return null;
124+
}
125+
126+
// 5. Calculate results
127+
128+
// Full Block: from 'declare module...' to matching '}'
129+
const fullBlock = {
130+
start: fullBlockStartIndex,
131+
length: closeBraceIndex - fullBlockStartIndex + 1
132+
};
133+
134+
// Inner Content: from char after '{' to char before '}'
135+
const innerContent = {
136+
start: openBraceIndex + 1,
137+
length: closeBraceIndex - (openBraceIndex + 1)
138+
};
139+
140+
return { fullBlock, innerContent };
141+
}

0 commit comments

Comments
 (0)