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