Skip to content

Commit

Permalink
Reserved page sub-properties to allow new keys in page to be create…
Browse files Browse the repository at this point in the history
…d downstream (page.excerpt or page.lang). Fixes #1173
  • Loading branch information
zachleat committed Apr 13, 2024
1 parent 70df967 commit 2647b94
Show file tree
Hide file tree
Showing 4 changed files with 100 additions and 47 deletions.
49 changes: 16 additions & 33 deletions src/Template.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ const debug = debugUtil("Eleventy:Template");
const debugDev = debugUtil("Dev:Eleventy:Template");

class EleventyTransformError extends EleventyBaseError {}
class EleventyReservedDataError extends TypeError {}

class Template extends TemplateContent {
constructor(templatePath, templateData, extensionMap, config) {
Expand Down Expand Up @@ -332,7 +331,7 @@ class Template extends TemplateContent {
debugDev("%o getData getTemplateDirectoryData and getGlobalData", this.inputPath);
}

let { data: frontMatterData, excerpt } = await this.getFrontMatterData();
let { data: frontMatterData } = await this.getFrontMatterData();
let layoutKey =
frontMatterData[this.config.keys.layout] ||
localData[this.config.keys.layout] ||
Expand All @@ -357,26 +356,20 @@ class Template extends TemplateContent {
frontMatterData,
);

let reserved = this.config.freezeReservedData ? ReservedData.getReservedKeys(mergedData) : [];
if (reserved.length > 0) {
let e = new EleventyReservedDataError(
`Cannot override reserved Eleventy properties: ${reserved.join(", ")}`,
);
e.reservedNames = reserved;
throw e;
if (this.config.freezeReservedData) {
ReservedData.check(mergedData);
}

this.addExcerpt(mergedData, excerpt);
mergedData = await this.addPageDate(mergedData);
mergedData = this.addPageData(mergedData);
await this.addPage(mergedData);

debugDev("%o getData mergedData", this.inputPath);

this._dataCache = mergedData;

return mergedData;
} catch (e) {
if (
e instanceof EleventyReservedDataError ||
ReservedData.isReservedDataError(e) ||
(e instanceof TypeError &&
e.message.startsWith("Cannot add property") &&
e.message.endsWith("not extensible"))
Expand All @@ -390,40 +383,25 @@ class Template extends TemplateContent {
}
}

async addPageDate(data) {
async addPage(data) {
if (!("page" in data)) {
data.page = {};
}

let newDate = await this.getMappedDate(data);

if ("page" in data && "date" in data.page) {
debug(
"Warning: data.page.date is in use (%o) will be overwritten with: %o",
data.page.date,
newDate,
);
}

data.page.date = newDate;

return data;
}

addPageData(data) {
if (!("page" in data)) {
data.page = {};
}

data.page.inputPath = this.inputPath;
data.page.fileSlug = this.fileSlugStr;
data.page.filePathStem = this.filePathStem;
data.page.outputFileExtension = this.engine.defaultTemplateFileExtension;
data.page.templateSyntax = this.templateRender.getEnginesList(
data[this.config.keys.engineOverride],
);

return data;
// data.page.url
// data.page.outputPath
// data.page.excerpt from gray-matter and Front Matter
// data.page.lang from I18nPlugin
}

// Tests only
Expand Down Expand Up @@ -573,6 +551,11 @@ class Template extends TemplateContent {
this,
);

// Check for reserved properties in computed data
if (this.config.freezeReservedData) {
ReservedData.check(data[this.config.keys.computed]);
}

// actually add the computed data
this._addComputedEntry(this.computedData, data[this.config.keys.computed]);

Expand Down
14 changes: 2 additions & 12 deletions src/TemplateContent.js
Original file line number Diff line number Diff line change
Expand Up @@ -180,16 +180,6 @@ class TemplateContent {
return this.config.virtualTemplates[inputDirRelativeInputPath];
}

addExcerpt(data, excerpt) {
if (!data || !excerpt) {
return;
}

// alias, defaults to page.excerpt
let alias = this.config.frontMatterParsingOptions?.excerpt_alias || "page.excerpt";
lodashSet(data, alias, excerpt);
}

async read() {
if (!this.readingPromise) {
if (!this.inputContent) {
Expand Down Expand Up @@ -232,8 +222,8 @@ class TemplateContent {
}

// alias, defaults to page.excerpt
// let alias = options.excerpt_alias || "page.excerpt";
// lodashSet(fm.data, alias, fm.excerpt);
let alias = options.excerpt_alias || "page.excerpt";
lodashSet(fm.data, alias, fm.excerpt);
}

// For monkey patchers that used `frontMatter` 🤧
Expand Down
58 changes: 56 additions & 2 deletions src/Util/ReservedData.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,64 @@
class EleventyReservedDataError extends TypeError {}

class ReservedData {
static properties = ["page", "content", "collections"];
static properties = [
// "pkg", // Object.freeze’d upstream
// "eleventy", // Object.freeze’d upstream
// "page" is only frozen for specific subproperties below
"content",
"collections",
];

static pageProperties = [
"date",
"inputPath",
"fileSlug",
"filePathStem",
"outputFileExtension",
"templateSyntax",
"url",
"outputPath",
// not yet `excerpt` or `lang` set via front matter and computed data
];

// Check in the data cascade for reserved data properties.
static getReservedKeys(data) {
return this.properties.filter((key) => {
let keys = this.properties.filter((key) => {
return key in data;
});

if ("page" in data) {
if (typeof data.page === "object") {
for (let key of this.pageProperties) {
if (key in data.page) {
keys.push(`page.${key}`);
}
}
} else {
// fail `page` when set to non-object values.
keys.push("page");
}
}
return keys;
}

static check(data) {
let reserved = ReservedData.getReservedKeys(data);
if (reserved.length === 0) {
return;
}

let error = new EleventyReservedDataError(
`Cannot override reserved Eleventy properties: ${reserved.join(", ")}`,
);

error.reservedNames = reserved;

throw error;
}

static isReservedDataError(e) {
return e instanceof EleventyReservedDataError;
}
}

Expand Down
26 changes: 26 additions & 0 deletions test/ReservedDataTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import test from "ava";
import ReservedData from "../src/Util/ReservedData.js";

test("No reserved Keys", t => {
t.deepEqual(ReservedData.getReservedKeys({ key: {} }).sort(), []);
});

test("Standard keys are reserved", t => {
t.deepEqual(ReservedData.getReservedKeys({ content: "" }).sort(), ["content"]);
t.deepEqual(ReservedData.getReservedKeys({ collections: {} }).sort(), ["collections"]);
t.deepEqual(ReservedData.getReservedKeys({ content: "", collections: {} }).sort(), ["collections", "content"]);
});

test("`page` subkeys", t => {
t.deepEqual(ReservedData.getReservedKeys({ page: {} }).sort(), []);
t.deepEqual(ReservedData.getReservedKeys({ page: "" }).sort(), ["page"]);
t.deepEqual(ReservedData.getReservedKeys({ page: { date: "", otherkey: "" } }).sort(), ["page.date"]);
t.deepEqual(ReservedData.getReservedKeys({ page: { inputPath: "", otherkey: "" } }).sort(), ["page.inputPath"]);
t.deepEqual(ReservedData.getReservedKeys({ page: { fileSlug: "", otherkey: "" } }).sort(), ["page.fileSlug"]);
t.deepEqual(ReservedData.getReservedKeys({ page: { filePathStem: "", otherkey: "" } }).sort(), ["page.filePathStem"]);
t.deepEqual(ReservedData.getReservedKeys({ page: { outputFileExtension: "", otherkey: "" } }).sort(), ["page.outputFileExtension"]);
t.deepEqual(ReservedData.getReservedKeys({ page: { templateSyntax: "", otherkey: "" } }).sort(), ["page.templateSyntax"]);
t.deepEqual(ReservedData.getReservedKeys({ page: { url: "", otherkey: "" } }).sort(), ["page.url"]);
t.deepEqual(ReservedData.getReservedKeys({ page: { outputPath: "", otherkey: "" } }).sort(), ["page.outputPath"]);
t.deepEqual(ReservedData.getReservedKeys({ page: { date: "", outputPath: "", otherkey: "" } }).sort(), ["page.date", "page.outputPath"]);
});

0 comments on commit 2647b94

Please sign in to comment.