Skip to content

Commit 2264008

Browse files
committed
feat: add jsonc support
1 parent 23b3534 commit 2264008

File tree

8 files changed

+82
-38
lines changed

8 files changed

+82
-38
lines changed

codify.json5

Lines changed: 0 additions & 22 deletions
This file was deleted.

src/orchestrators/import.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,15 +347,15 @@ ${JSON.stringify(unsupportedTypeIds)}`);
347347
const folderPath = path.join(cwd, 'codify-imports')
348348
await FileUtils.createFolder(folderPath)
349349

350-
let fileName = path.join(folderPath, 'import.codify.json')
350+
let fileName = path.join(folderPath, 'import.codify.jsonc')
351351
let counter = 1;
352352

353353
while(true) {
354354
if (!(await FileUtils.fileExists(fileName))) {
355355
return fileName;
356356
}
357357

358-
fileName = path.join(folderPath, `import-${counter}.codify.json`);
358+
fileName = path.join(folderPath, `import-${counter}.codify.jsonc`);
359359
counter++;
360360
}
361361
}

src/parser/entities.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,6 @@ export interface LanguageSpecificParser {
1919
export enum FileType {
2020
JSON = 'json',
2121
YAML = 'yaml',
22-
JSON5 = 'json5'
22+
JSON5 = 'json5',
23+
JSONC = 'jsonc',
2324
}

src/parser/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,19 @@ import { ConfigFactory } from './config-factory.js';
88
import { FileType, InMemoryFile, ParsedConfig } from './entities.js';
99
import { JsonParser } from './json/json-parser.js';
1010
import { Json5Parser } from './json5/json-parser.js';
11+
import { JsoncParser } from './jsonc/json-parser.js';
1112
import { FileReader } from './reader.js';
1213
import { SourceMapCache } from './source-maps.js';
1314
import { YamlParser } from './yaml/yaml-parser.js';
1415

15-
export const CODIFY_FILE_REGEX = /^(.*)?codify(.json|.yaml|.json5)$/;
16+
export const CODIFY_FILE_REGEX = /^(.*)?codify(.json|.yaml|.json5|.jsonc)$/;
1617

1718
class Parser {
1819
private readonly languageSpecificParsers= {
1920
[FileType.JSON]: new JsonParser(),
2021
[FileType.YAML]: new YamlParser(),
21-
[FileType.JSON5]: new Json5Parser()
22+
[FileType.JSON5]: new Json5Parser(),
23+
[FileType.JSONC]: new JsoncParser()
2224
}
2325

2426
async parse(dirOrFile: string): Promise<Project> {

src/parser/jsonc/json-parser.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import JsonSourceMap from '@mischnic/json-sourcemap';
2+
import { Config, ConfigFileSchema } from 'codify-schemas';
3+
import jju from 'jju'
4+
5+
import { AjvValidationError, SyntaxError } from '../../common/errors.js';
6+
import { ajv } from '../../utils/ajv.js';
7+
import { InMemoryFile, LanguageSpecificParser, ParsedConfig } from '../entities.js';
8+
import { SourceMapCache } from '../source-maps.js';
9+
10+
const validator = ajv.compile(ConfigFileSchema);
11+
12+
export class JsoncParser implements LanguageSpecificParser {
13+
parse(file: InMemoryFile, sourceMaps?: SourceMapCache): ParsedConfig[] {
14+
let content;
15+
try {
16+
content = jju.parse(file.contents);
17+
18+
if (sourceMaps) {
19+
sourceMaps.addSourceMap(file, JsonSourceMap.parse(file.contents, undefined, { dialect: 'JSON5' }));
20+
}
21+
} catch (error) {
22+
throw new SyntaxError({
23+
fileName: file.filePath,
24+
message: (error as Error).message,
25+
});
26+
}
27+
28+
if (!this.validate(content)) {
29+
throw new AjvValidationError('invalid config file', validator.errors!, file.filePath, sourceMaps);
30+
}
31+
32+
return content.map((contents, idx) => ({
33+
contents,
34+
sourceMapKey: SourceMapCache.constructKey(file.filePath, `/${idx}`)
35+
}))
36+
}
37+
38+
private validate(content: unknown): content is Config[] {
39+
return validator(content);
40+
}
41+
}

src/parser/reader.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ export class FileReader {
2626
if (filePath.endsWith('.json5')) {
2727
return FileType.JSON5
2828
}
29+
30+
if (filePath.endsWith('.jsonc')) {
31+
return FileType.JSONC
32+
}
2933

3034
throw new InternalError(`Unsupported file type passed to FileReader. File path: ${filePath}`);
3135
}

src/parser/source-maps.ts

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,12 @@ export class SourceMapCache {
4444
}
4545

4646
addSourceMap(file: InMemoryFile, sourceMap: JsonSourceMap | YamlSourceMap) {
47-
if (file.fileType === FileType.JSON) {
48-
this.sourceMaps.set(file.filePath, {
49-
file,
50-
sourceMap: new JsonSourceMapAdapter(sourceMap as JsonSourceMap),
51-
})
52-
} else if (file.fileType === FileType.YAML) {
47+
if (file.fileType === FileType.YAML) {
5348
this.sourceMaps.set(file.filePath, {
5449
file,
5550
sourceMap: new YamlSourceMapAdapter(sourceMap as YamlSourceMap, file),
5651
})
57-
} else if (file.fileType === FileType.JSON5) {
52+
} else {
5853
this.sourceMaps.set(file.filePath, {
5954
file,
6055
sourceMap: new JsonSourceMapAdapter(sourceMap as JsonSourceMap),

src/utils/file-modification-calculator.ts

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ export class FileModificationCalculator {
9090
.map((r) => r.resource)
9191
const insertionIndex = newFile.length - 2; // Last element is guarenteed to be the closing bracket. We insert 1 before that
9292

93-
newFile = this.insert(newFile, newResourcesToInsert, insertionIndex);
93+
newFile = this.insert(newFile, this.existingFile.fileType, newResourcesToInsert, insertionIndex);
9494

9595
const lastCharacterIndex = this.existingFile.contents.lastIndexOf(']')
9696
if (lastCharacterIndex < this.existingFile.contents.length - 1) {
@@ -110,8 +110,8 @@ export class FileModificationCalculator {
110110
return;
111111
}
112112

113-
if (this.existingFile?.fileType !== FileType.JSON && this.existingFile?.fileType !== FileType.JSON5) {
114-
throw new Error(`Only updating .json and .json5 files are currently supported. Found ${this.existingFile?.filePath}`);
113+
if (this.existingFile?.fileType !== FileType.JSON && this.existingFile?.fileType !== FileType.JSON5 && this.existingFile?.fileType !== FileType.JSONC) {
114+
throw new Error(`Only updating .json, .json5, and .jsonc files are currently supported. Found ${this.existingFile?.filePath}`);
115115
}
116116

117117
if (this.existingConfigs.some((r) => !r.resourceInfo)) {
@@ -138,6 +138,7 @@ export class FileModificationCalculator {
138138
// Insert always works at the end
139139
private insert(
140140
file: string,
141+
fileType: FileType,
141142
resources: ResourceConfig[],
142143
position: number,
143144
): string {
@@ -147,7 +148,13 @@ export class FileModificationCalculator {
147148

148149
for (const newResource of resources.reverse()) {
149150
const sortedResource = { ...newResource.core(true), ...this.sortKeys(newResource.parameters) }
150-
let content = jju.stringify(sortedResource, fileStyle as any);
151+
let content = jju.stringify(sortedResource, {
152+
indent: fileStyle.indent,
153+
no_trailing_comma: true,
154+
quote: '"',
155+
quote_keys: fileStyle.quote_keys,
156+
mode: this.fileTypeString(fileType),
157+
});
151158

152159
content = content.split(/\n/).map((l) => `${this.indentString}${l}`).join('\n')
153160
content = `,\n${content}`;
@@ -254,4 +261,20 @@ export class FileModificationCalculator {
254261
})
255262
)
256263
}
264+
265+
private fileTypeString(fileType: FileType): 'json' | 'json5' | 'cjson' {
266+
if (fileType === FileType.JSON) {
267+
return 'json'
268+
}
269+
270+
if (fileType === FileType.JSON5) {
271+
return 'json5'
272+
}
273+
274+
if (fileType === FileType.JSONC) {
275+
return 'cjson'
276+
}
277+
278+
throw new Error(`Unsupported file type ${fileType} when trying to generate new configs`);
279+
}
257280
}

0 commit comments

Comments
 (0)