Skip to content

Commit 3a1d1ac

Browse files
authored
Merge pull request #12388 from quarto-dev/bugfix/safe-clone-deep
Bugfix/safe clone deep
2 parents 26a0966 + c195ae2 commit 3a1d1ac

File tree

14 files changed

+97
-33
lines changed

14 files changed

+97
-33
lines changed

src/command/render/pandoc-dependencies-html.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { ensureDirSync } from "../../deno_ral/fs.ts";
3535
import { ProjectContext } from "../../project/types.ts";
3636
import { projectOutputDir } from "../../project/project-shared.ts";
3737
import { insecureHash } from "../../core/hash.ts";
38+
import { safeCloneDeep } from "../../core/safe-clone-deep.ts";
3839

3940
export async function writeDependencies(
4041
dependenciesFile: string,
@@ -146,7 +147,7 @@ export function resolveDependencies(
146147
project?: ProjectContext,
147148
) {
148149
// deep copy to not mutate caller's object
149-
extras = ld.cloneDeep(extras);
150+
extras = safeCloneDeep(extras);
150151

151152
const lines: string[] = [];
152153
const afterBodyLines: string[] = [];

src/command/render/pandoc-html.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import { join } from "../../deno_ral/path.ts";
8-
import { cloneDeep, uniqBy } from "../../core/lodash.ts";
8+
import { uniqBy } from "../../core/lodash.ts";
99

1010
import {
1111
Format,
@@ -39,6 +39,7 @@ import { md5HashBytes } from "../../core/hash.ts";
3939
import { InternalError } from "../../core/lib/error.ts";
4040
import { assert } from "testing/asserts";
4141
import { safeModeFromFile } from "../../deno_ral/fs.ts";
42+
import { safeCloneDeep } from "../../core/safe-clone-deep.ts";
4243

4344
// The output target for a sass bundle
4445
// (controls the overall style tag that is emitted)
@@ -54,7 +55,7 @@ export async function resolveSassBundles(
5455
format: Format,
5556
project: ProjectContext,
5657
) {
57-
extras = cloneDeep(extras);
58+
extras = safeCloneDeep(extras);
5859

5960
const mergedBundles: Record<string, SassBundleWithBrand[]> = {};
6061

@@ -102,7 +103,7 @@ export async function resolveSassBundles(
102103
const userBrand = bundle.user?.findIndex((layer) => layer === "brand");
103104
let cloned = false;
104105
if (userBrand && userBrand !== -1) {
105-
bundle = cloneDeep(bundle);
106+
bundle = safeCloneDeep(bundle);
106107
cloned = true;
107108
bundle.user!.splice(userBrand, 1, ...(maybeBrandBundle?.user || []));
108109
foundBrand.light = true;
@@ -112,7 +113,7 @@ export async function resolveSassBundles(
112113
);
113114
if (darkBrand && darkBrand !== -1) {
114115
if (!cloned) {
115-
bundle = cloneDeep(bundle);
116+
bundle = safeCloneDeep(bundle);
116117
}
117118
bundle.dark!.user!.splice(
118119
darkBrand,
@@ -158,7 +159,7 @@ export async function resolveSassBundles(
158159

159160
// Provide a dark bundle for this
160161
const darkBundles = bundles.map((bundle) => {
161-
bundle = cloneDeep(bundle);
162+
bundle = safeCloneDeep(bundle);
162163
bundle.user = bundle.dark?.user || bundle.user;
163164
bundle.quarto = bundle.dark?.quarto || bundle.quarto;
164165
bundle.framework = bundle.dark?.framework || bundle.framework;
@@ -362,7 +363,7 @@ async function resolveQuartoSyntaxHighlighting(
362363
return extras;
363364
}
364365

365-
extras = cloneDeep(extras);
366+
extras = safeCloneDeep(extras);
366367

367368
// If we're using default highlighting, use theme darkness to select highlight style
368369
const mediaAttr = attribForThemeStyle(style);

src/command/render/pandoc.ts

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ import { isWindows } from "../../deno_ral/platform.ts";
203203
import { appendToCombinedLuaProfile } from "../../core/performance/perfetto-utils.ts";
204204
import { makeTimedFunctionAsync } from "../../core/performance/function-times.ts";
205205
import { walkJson } from "../../core/json.ts";
206+
import { safeCloneDeep } from "../../core/safe-clone-deep.ts";
207+
import { assert } from "testing/asserts";
206208
import { call } from "../../deno_ral/process.ts";
207209

208210
// in case we are running multiple pandoc processes
@@ -323,7 +325,9 @@ export async function runPandoc(
323325
JSON.stringify(paramsJson),
324326
);
325327

326-
const traceFilters = pandocMetadata?.["_quarto"]?.["trace-filters"] ||
328+
const traceFilters =
329+
// deno-lint-ignore no-explicit-any
330+
(pandocMetadata as any)?.["_quarto"]?.["trace-filters"] ||
327331
Deno.env.get("QUARTO_TRACE_FILTERS");
328332

329333
if (traceFilters) {
@@ -438,7 +442,7 @@ export async function runPandoc(
438442

439443
// generate defaults and capture defaults to be printed
440444
let allDefaults = (await generateDefaults(options)) || {};
441-
let printAllDefaults = ld.cloneDeep(allDefaults) as FormatPandoc;
445+
let printAllDefaults = safeCloneDeep(allDefaults);
442446

443447
// capture any filterParams in the FormatExtras
444448
const formatFilterParams = {} as Record<string, unknown>;
@@ -1120,7 +1124,7 @@ export async function runPandoc(
11201124

11211125
// selectively overwrite some resolved metadata (e.g. ensure that metadata
11221126
// computed from inline r expressions gets included @ the bottom).
1123-
const pandocMetadata = ld.cloneDeep(options.format.metadata || {});
1127+
const pandocMetadata = safeCloneDeep(options.format.metadata || {});
11241128
for (const key of Object.keys(engineMetadata)) {
11251129
const isChapterTitle = key === kTitle && projectIsBook(options.project);
11261130

@@ -1154,6 +1158,7 @@ export async function runPandoc(
11541158
dateFields.forEach((dateField) => {
11551159
const date = pandocMetadata[dateField];
11561160
const format = pandocMetadata[kDateFormat];
1161+
assert(format === undefined || typeof format === "string");
11571162
pandocMetadata[dateField] = resolveAndFormatDate(
11581163
options.source,
11591164
date,
@@ -1173,15 +1178,19 @@ export async function runPandoc(
11731178
// Expand citation dates into CSL dates
11741179
const citationMetadata = pandocMetadata[kCitation];
11751180
if (citationMetadata) {
1181+
assert(typeof citationMetadata === "object");
1182+
// ideally we should be asserting non-arrayness here but that's not very fast.
1183+
// assert(!Array.isArray(citationMetadata));
1184+
const citationMetadataObj = citationMetadata as Record<string, unknown>;
11761185
const docCSLDate = dateRaw
11771186
? cslDate(resolveDate(options.source, dateRaw))
11781187
: undefined;
11791188
const fields = ["issued", "available-date"];
11801189
fields.forEach((field) => {
1181-
if (citationMetadata[field]) {
1182-
citationMetadata[field] = cslDate(citationMetadata[field]);
1190+
if (citationMetadataObj[field]) {
1191+
citationMetadataObj[field] = cslDate(citationMetadataObj[field]);
11831192
} else if (docCSLDate) {
1184-
citationMetadata[field] = docCSLDate;
1193+
citationMetadataObj[field] = docCSLDate;
11851194
}
11861195
});
11871196
}
@@ -1221,8 +1230,9 @@ export async function runPandoc(
12211230
!isBeamerOutput(options.format.pandoc)
12221231
) {
12231232
const docClass = pandocMetadata[kDocumentClass];
1233+
assert(!docClass || typeof docClass === "string");
12241234
const isPrintDocumentClass = docClass &&
1225-
["book", "scrbook"].includes(docClass);
1235+
["book", "scrbook"].includes(docClass as string);
12261236

12271237
if (!isPrintDocumentClass) {
12281238
if (pandocMetadata[kColorLinks] === undefined) {
@@ -1274,7 +1284,7 @@ export async function runPandoc(
12741284
prefix: "quarto-metadata",
12751285
suffix: ".yml",
12761286
});
1277-
const pandocPassedMetadata = ld.cloneDeep(pandocMetadata);
1287+
const pandocPassedMetadata = safeCloneDeep(pandocMetadata);
12781288
delete pandocPassedMetadata.format;
12791289
delete pandocPassedMetadata.project;
12801290
delete pandocPassedMetadata.website;
@@ -1673,7 +1683,7 @@ function runPandocMessage(
16731683

16741684
const keys = Object.keys(metadata);
16751685
if (keys.length > 0) {
1676-
const printMetadata = ld.cloneDeep(metadata) as Metadata;
1686+
const printMetadata = safeCloneDeep(metadata);
16771687
delete printMetadata.format;
16781688

16791689
// print message

src/command/render/render-contexts.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ import {
8888
} from "../../core/pandoc/pandoc-formats.ts";
8989
import { ExtensionContext } from "../../extension/types.ts";
9090
import { NotebookContext } from "../../render/notebook/notebook-types.ts";
91+
import { safeCloneDeep } from "../../core/safe-clone-deep.ts";
9192

9293
export async function resolveFormatsFromMetadata(
9394
metadata: Metadata,
@@ -215,7 +216,7 @@ export async function renderContexts(
215216
// we make it optional because some of the callers have
216217
// actually just cloned it themselves and don't need to preserve
217218
// the original
218-
options = ld.cloneDeep(options) as RenderOptions;
219+
options = safeCloneDeep(options);
219220
}
220221

221222
const { engine, target } = await fileExecutionEngineAndTarget(
@@ -342,8 +343,8 @@ function mergeQuartoConfigs(
342343
...configs: Array<Metadata>
343344
): Metadata {
344345
// copy all configs so we don't mutate them
345-
config = ld.cloneDeep(config);
346-
configs = ld.cloneDeep(configs);
346+
config = safeCloneDeep(config);
347+
configs = safeCloneDeep(configs);
347348

348349
// bibliography needs to always be an array so it can be merged
349350
const fixupMergeableScalars = (metadata: Metadata) => {
@@ -450,7 +451,7 @@ async function resolveFormats(
450451

451452
// Remove any 'to' information that will force the
452453
// rendering to a particular format
453-
options = ld.cloneDeep(options);
454+
options = safeCloneDeep(options);
454455
delete options.flags?.to;
455456
}
456457

@@ -753,10 +754,10 @@ export async function projectMetadataForInputFile(
753754
projectType(project.config?.project?.[kProjectType]),
754755
project.dir,
755756
dirname(input),
756-
ld.cloneDeep(project.config),
757+
safeCloneDeep(project.config),
757758
) as Metadata;
758759
} else {
759760
// Just return the config or empty metadata
760-
return ld.cloneDeep(project.config) || {};
761+
return safeCloneDeep(project.config) || {};
761762
}
762763
}

src/config/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -457,7 +457,7 @@ export interface Format {
457457

458458
export interface LightDarkBrand {
459459
[kLight]?: Brand;
460-
[kDark]?: Brand
460+
[kDark]?: Brand;
461461
}
462462

463463
export interface FormatRender {

src/render/notebook/notebook-contributor-html.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ function resolveHtmlNotebook(
8484
executedFile: ExecutedFile,
8585
notebookMetadata?: NotebookMetadata,
8686
) {
87-
const resolved = safeCloneDeep(executedFile) as ExecutedFile;
87+
const resolved = safeCloneDeep(executedFile);
8888

8989
// Set the output file
9090
resolved.recipe.format.pandoc[kOutputFile] = `${outputFile(nbAbsPath)}`;

src/resources/editor/tools/vs-code.mjs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8371,6 +8371,10 @@ var require_yaml_intelligence_resources = __commonJS({
83718371
}
83728372
]
83738373
},
8374+
{
8375+
id: "date-format",
8376+
schema: "string"
8377+
},
83748378
{
83758379
id: "math-methods",
83768380
enum: {
@@ -12670,6 +12674,13 @@ var require_yaml_intelligence_resources = __commonJS({
1267012674
},
1267112675
description: "Document date"
1267212676
},
12677+
{
12678+
name: "date-format",
12679+
schema: {
12680+
ref: "date-format"
12681+
},
12682+
description: "Date format for the document"
12683+
},
1267312684
{
1267412685
name: "date-modified",
1267512686
tags: {
@@ -24332,12 +24343,12 @@ var require_yaml_intelligence_resources = __commonJS({
2433224343
mermaid: "%%"
2433324344
},
2433424345
"handlers/mermaid/schema.yml": {
24335-
_internalId: 195562,
24346+
_internalId: 195861,
2433624347
type: "object",
2433724348
description: "be an object",
2433824349
properties: {
2433924350
"mermaid-format": {
24340-
_internalId: 195554,
24351+
_internalId: 195853,
2434124352
type: "enum",
2434224353
enum: [
2434324354
"png",
@@ -24353,7 +24364,7 @@ var require_yaml_intelligence_resources = __commonJS({
2435324364
exhaustiveCompletions: true
2435424365
},
2435524366
theme: {
24356-
_internalId: 195561,
24367+
_internalId: 195860,
2435724368
type: "anyOf",
2435824369
anyOf: [
2435924370
{

src/resources/editor/tools/yaml/web-worker.js

Lines changed: 14 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/resources/editor/tools/yaml/yaml-intelligence-resources.json

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1343,6 +1343,10 @@
13431343
}
13441344
]
13451345
},
1346+
{
1347+
"id": "date-format",
1348+
"schema": "string"
1349+
},
13461350
{
13471351
"id": "math-methods",
13481352
"enum": {
@@ -5642,6 +5646,13 @@
56425646
},
56435647
"description": "Document date"
56445648
},
5649+
{
5650+
"name": "date-format",
5651+
"schema": {
5652+
"ref": "date-format"
5653+
},
5654+
"description": "Date format for the document"
5655+
},
56455656
{
56465657
"name": "date-modified",
56475658
"tags": {
@@ -17304,12 +17315,12 @@
1730417315
"mermaid": "%%"
1730517316
},
1730617317
"handlers/mermaid/schema.yml": {
17307-
"_internalId": 195562,
17318+
"_internalId": 195861,
1730817319
"type": "object",
1730917320
"description": "be an object",
1731017321
"properties": {
1731117322
"mermaid-format": {
17312-
"_internalId": 195554,
17323+
"_internalId": 195853,
1731317324
"type": "enum",
1731417325
"enum": [
1731517326
"png",
@@ -17325,7 +17336,7 @@
1732517336
"exhaustiveCompletions": true
1732617337
},
1732717338
"theme": {
17328-
"_internalId": 195561,
17339+
"_internalId": 195860,
1732917340
"type": "anyOf",
1733017341
"anyOf": [
1733117342
{

0 commit comments

Comments
 (0)