Skip to content

Commit

Permalink
fix: finally real obj parsing in the prebuilder codebase
Browse files Browse the repository at this point in the history
  • Loading branch information
gustaveWPM committed Feb 26, 2024
1 parent 42d4c1c commit c5cacb4
Show file tree
Hide file tree
Showing 16 changed files with 162 additions and 17 deletions.
3 changes: 2 additions & 1 deletion .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@ module.exports = {
'no-unreachable': ERROR,
'require-await': ERROR,
'no-unused-vars': OFF,
'import/first': ERROR
'import/first': ERROR,
'no-eval': ERROR
},

extends: [
Expand Down
4 changes: 2 additions & 2 deletions doc/i18n/00.quirks.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ vocabulary, since avoiding hard-coded duplicate values is essential in such data
Another motivation was also to maintain a _prebuilder_ code that is as simple and predictable as possible.

The _prebuilder_ is a standalone NPM package. It does not "import" any scripts from the codebase. Instead, it opens them as files via the _filesystem_
and juggles between parsing and raw `eval` function calls (which could be also `JSON.parse` calls... but still).
and parses them.

The ability to dynamically define values based on variables scattered in different parts of the code made implementing this feature nightmarish. In my
opinion, this indicated a poor design choice rather than a flawed implementation of the _prebuilder_.
Expand Down Expand Up @@ -69,7 +69,7 @@ Likewise:
export default {
// * ...
_infos: {
lng: '__SCANNED_ON_PREBUILD_FIELD__'
lng: '__SCANNED__'
},

navbar: {
Expand Down
8 changes: 5 additions & 3 deletions packages/prebuilder/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,13 @@
"start": "tsc --build && node prebuilder-dist/src/main.js"
},
"dependencies": {
"@babel/core": "^7.23.9",
"@babel/parser": "^7.23.9",
"arg": "^5.0.2",
"inflection": "^3.0.0",
"chokidar": "^3.6.0",
"format-message": "6.2.4",
"ts-morph": "21.0.1",
"chokidar": "^3.6.0"
"inflection": "^3.0.0",
"ts-morph": "21.0.1"
},
"devDependencies": {
"@rtm/shared-types": "*",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { afterAll, describe, expect, it } from 'vitest';

import generateBlogArchitectureType from '../blogArchitectureType';

// https://github.com/vitest-dev/vitest/discussions/2484
const fs = require('fs/promises');

const __TARGET_FOLDER_ROOT = './packages/prebuilder/src/generators/blog/__tests__/FAKE_CODEGEN';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { afterAll, describe, expect, it } from 'vitest';

import generateBlogType from '../blogType';

// https://github.com/vitest-dev/vitest/discussions/2484
const fs = require('fs/promises');

const __TARGET_FOLDER_ROOT = './packages/prebuilder/src/generators/blog/__tests__/FAKE_CODEGEN';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { afterAll, describe, expect, it } from 'vitest';

import generateI18nBlogCategories from '../i18nBlogCategories';

// https://github.com/vitest-dev/vitest/discussions/2484
const fs = require('fs/promises');

const __TARGET_FOLDER_ROOT = './packages/prebuilder/src/generators/blog/__tests__/FAKE_CODEGEN';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { afterAll, describe, expect, it } from 'vitest';

import generateI18nPagesTitles from '../i18nPagesTitles';

// https://github.com/vitest-dev/vitest/discussions/2484
const fs = require('fs/promises');

const __TARGET_FOLDER_ROOT = './packages/prebuilder/src/generators/blog/__tests__/FAKE_CODEGEN';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { afterAll, describe, expect, it } from 'vitest';

import generateDefaultLanguageTokenType from '../defaultLanguageTokenType';

// https://github.com/vitest-dev/vitest/discussions/2484
const fs = require('fs/promises');

const __TARGET_FOLDER_ROOT = './packages/prebuilder/src/generators/defaultLanguageToken/__tests__/FAKE_CODEGEN';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { afterAll, describe, expect, it } from 'vitest';

import generateIndexFile from '../indexFile';

// https://github.com/vitest-dev/vitest/discussions/2484
const fs = require('fs/promises');

const __TARGET_FOLDER_ROOT = './packages/prebuilder/src/generators/index/__tests__/FAKE_CODEGEN';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { afterAll, describe, expect, it } from 'vitest';

import generateLandingPagesType from '../lpType';

// https://github.com/vitest-dev/vitest/discussions/2484
const fs = require('fs/promises');

const __TARGET_FOLDER_ROOT = './packages/prebuilder/src/generators/lp/__tests__/FAKE_CODEGEN';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import type { Page } from '../../../types/Metadatas';

import generatePagesType, { HARDCODED_FALLBACK_TYPE_FIELDS } from '../pagesType';

// https://github.com/vitest-dev/vitest/discussions/2484
const fs = require('fs/promises');

const __TARGET_FOLDER_ROOT = './packages/prebuilder/src/generators/pages/__tests__/FAKE_CODEGEN';
Expand Down
87 changes: 83 additions & 4 deletions packages/prebuilder/src/lib/__tests__/prebuild.etc.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,93 @@
// eslint-disable-next-line import/no-extraneous-dependencies
import { describe, expect, it } from 'vitest';

import getRawDataFromBracesDeclaration from '../getRawDataFromBracesDeclaration';
import { objInnerToObj } from '../etc';

describe('objInnerToObj', () => {
it('should throw when Babel parsing fails', () => {
const objInner = 'foo: bar';
expect(() => objInnerToObj(objInner as string)).toThrow();
});

it('should return an obj when parsing succeeds', () => {
const objInner = '"foo": "bar"';
expect(objInnerToObj(objInner as string)).toStrictEqual({ foo: 'bar' });
});

it('should return an obj, given a valid obj inner', () => {
const objInner = `
const initialObj = { foo: 'bar', bar: 'foo' };
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
const objInner = getRawDataFromBracesDeclaration(JSON.stringify(initialObj), 0);
expect(objInner).not.toBe(null);
expect(objInnerToObj(objInner as string)).toStrictEqual(initialObj);
});

it('should return an obj, given a valid obj inner (nested)', () => {
const initialObj = {
baz: {
baz: {
bar: 'foo'
},
foo: 'bar',
bar: 'foo'
},
foo: 'bar',
bar: 'foo',
`;
expect(objInnerToObj(objInner)).toStrictEqual({ foo: 'bar', bar: 'foo' });
bar: 'foo'
};

const objInner = getRawDataFromBracesDeclaration(
JSON.stringify(initialObj),
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
0
);
expect(objInner).not.toBe(null);
expect(objInnerToObj(objInner as string)).toStrictEqual(initialObj);
});

it('should return an obj, given a valid obj inner (literals)', () => {
const objInner = "foo: 'bar', bar: 'foo'";
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
expect(objInnerToObj(objInner as string)).toStrictEqual({ foo: 'bar', bar: 'foo' });
});

it('should return an obj, given a valid obj inner (string literals)', () => {
const objInner = "'foo': 'bar', 'bar': 'foo'";
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
expect(objInnerToObj(objInner as string)).toStrictEqual({ foo: 'bar', bar: 'foo' });
});

it('should throw, given an invalid obj inner (numeric literals)', () => {
const objInner = "2172183: 'bar', 211838173: 'foo'";
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
expect(() => objInnerToObj(objInner as string)).toThrow();
});

it('should throw, given an invalid obj inner (int values)', () => {
const objInner = 'foo: 45, bar: 12';
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
expect(() => objInnerToObj(objInner as string)).toThrow();
});

it('should throw when encountering unsupported value type', () => {
const objInner = 'foo: /regex/';
expect(() => objInnerToObj(objInner as string)).toThrow();
});

it('should throw, given stupid input (random number)', () => {
const objInner = '88909988799';
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
expect(() => objInnerToObj(objInner as string)).toThrow();
});

it('should return empty object, given empty string input', () => {
const objInner = '';
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
expect(objInnerToObj(objInner as string)).toStrictEqual({});
});

it('should throw when JSON parsing fails', () => {
const objInner = 'invalid json';
expect(() => objInnerToObj(objInner as string)).toThrow();
});
});
51 changes: 49 additions & 2 deletions packages/prebuilder/src/lib/etc.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,49 @@
// eslint-disable-next-line
export const objInnerToObj = (objInner: string): any => eval('({\n' + objInner + '\n})');
import { parse } from '@babel/parser';

import type { I18nJSONPart } from '../types/Metadatas';

export function objInnerToObj(objInner: string): I18nJSONPart {
let res = {};
try {
const obj = JSON.parse('{\n' + objInner + '\n}');
return obj;
} catch {
try {
const parsedObject = parse(`({${objInner}})`, {
sourceType: 'unambiguous',
plugins: []
});

// eslint-disable-next-line @typescript-eslint/no-magic-numbers
if (parsedObject.program.body[0]?.type === 'ExpressionStatement' && parsedObject.program.body[0].expression.type === 'ObjectExpression') {
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
const objExpression = parsedObject.program.body[0].expression;
const obj: I18nJSONPart = objExpression.properties.reduce((accumulator: I18nJSONPart, prop) => {
if (prop.type === 'ObjectProperty') {
let key: string;
if (prop.key.type === 'Identifier') {
key = prop.key.name;
} else if (prop.key.type === 'StringLiteral') {
key = prop.key.value;
} else {
throw new Error(`Unsupported key type: ${prop.key.type}`);
}

let value: string | null = null;
if (prop.value.type === 'StringLiteral') {
value = prop.value.value;
} else {
throw new Error(`Unsupported value type: ${prop.value.type}`);
}
accumulator[key] = value;
}
return accumulator;
}, {});
res = obj;
}
} catch (babelError) {
throw babelError;
}
}
return res;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ import { INVALID_NESTINGS_NEEDLE, INVALID_NESTING_NEEDLE, INVALID_SLUGS_NEEDLE,
import { describe, expect, it, vi } from 'vitest';
// eslint-disable-next-line import/no-extraneous-dependencies
import { INVALID_PATH } from '𝕍/commons';
import path from 'path';

import type { VocabKey } from '../../config/translations';

import formatMessage from '../../config/formatMessage';
import sysPagesValidator from '../sysPages';

// https://github.com/vitest-dev/vitest/discussions/2484
const path = require('path');

const VALID_PAGES_FOLDER = path.normalize('./packages/prebuilder/src/validators/__tests__/fake_pages_folders/valid_fake_pages_folder');

const VALID_PAGES_FOLDER_WITH_ONE_UGLY_INDEX_STRATEGY = path.normalize(
Expand Down
6 changes: 6 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions src/i18n/locales/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,17 +76,17 @@ export default {
login: _
},

'pages-titles': SHARED_VOCAB_SCHEMA['pages-titles'],

_infos: {
lng: '__SCANNED_ON_PREBUILD_FIELD__'
lng: '__SCANNED__'
},

'pages-titles': SHARED_VOCAB_SCHEMA['pages-titles'],

'blog-categories': blogCategories
} as const satisfies TypedLeafsJSONData<MaybeScanned>;

type NotScanned = '';
type Scanned = '__SCANNED_ON_PREBUILD_FIELD__';
type Scanned = '__SCANNED__';
type MaybeScanned = NotScanned | Scanned;

// Stryker restore all
Expand Down

0 comments on commit c5cacb4

Please sign in to comment.