ts-morph
ts-morph copied to clipboard
Support automatically determining if a string literal should be written for a property key or value in Writers.object
Describe the bug
Version: 10.1.0
I want to generate .ts file with variable content, that depend on my existedObject
Writers generate incorrect object representation
To Reproduce
import { Project, VariableDeclarationKind, Writers } from "ts-morph";
const project = new Project();
const sourceFile = project.createSourceFile("test.ts", ``);
const existedObject = {
id: "some-id",
"quote-key": 2
}
sourceFile.addVariableStatement({
isExported: true,
declarationKind: VariableDeclarationKind.Const,
declarations: [{
name: 'flavor',
initializer: Writers.object(existedObject),
}]
})
console.log(sourceFile.getFullText())
Output:
export const flavor = {
id: some-id,
quote-key: 2
};
Expected behavior
export const flavor = {
id: "some-id",
"quote-key": 2
};
@whalemare I wouldn't consider this totally broken.
Right now, you need to explicitly write them as string literals. For example:
const existedObject = {
id: `"some-id"`,
'"quote-key"': 2
}
That said, I believe that the code could automatically determine that these should be string literals without having to do some complex parsing. I've renamed the title for the possible action item here.
I write some hacky code that close my needs, maybe it can help somebody.
P.s. for some reasons, I need modify parts with value for specific keys, so this code contains Mappers declaration that help to achieve this
import Writer from 'code-block-writer'
import { match } from 'ts-pattern'
type Wrappers = { [key in string]: { start: string; end: string } }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export function writeAny(writer: Writer, raw: any, wrappers: Wrappers = {}, needComma = false) {
match(typeof raw)
.with('object', () => writeObject(writer, raw, wrappers, true))
.with('string', () => writer.quote(raw))
.with('number', () => {
writer.write(`${raw}`)
})
.with('bigint', () => {
writer.write(`${raw}`)
})
.with('boolean', () => {
writer.write(raw ? 'true' : 'false')
})
.with('symbol', () => {
writer.quote(`${raw}`)
})
.otherwise(() => {
if (raw === null) {
writer.write('null')
} else if (raw === undefined) {
writer.write('undefined')
}
})
if (needComma) {
writer.write(',')
}
}
// eslint-disable-next-line @typescript-eslint/ban-types
export const writeObject = (writer: Writer, raw: object, wrappers: Wrappers = {}, needComma: boolean) => {
if (Array.isArray(raw)) {
writer.write(JSON.stringify(raw))
} else {
writer.write('{')
Object.keys(raw).forEach((key) => {
const spec = new RegExp('[^A-Za-z0-9]')
const hasSpecSymbols = spec.test(key)
writer.newLineIfLastNot()
if (hasSpecSymbols) {
writer.quote(key)
} else {
writer.write(key)
}
writer.write(`: ${wrappers[key]?.start ? wrappers[key].start : ''}`)
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
writeAny(writer, raw[key], wrappers, needComma)
if (wrappers[key]?.end) {
writer.write(wrappers[key].end)
}
})
writer.write('}')
}
}
Usage:
sourceFile.addVariableStatement({
isExported: true,
declarationKind: VariableDeclarationKind.Const,
declarations: [
{
name: 'flavor',
initializer: (writer) => {
return writeAny(writer, someObject, {
// this will patch values with key 'textStyle' by inserting in start _StyleSheet.create(_ and in the end _),_)
textStyle: {
start: 'StyleSheet.create(',
end: '),',
},
})
},
},
],
})
Hello @dsherret ,
I'm facing similar issue with wrong generated property names. Currently I'm using ts-morph to generate runtime client from WSDL (SOAP) and I have to make sure that generated propnames are same as in WSDL file. But some names in WSDL contains chars like -,. which are not correct property name characters... check this issue https://github.com/dderevjanik/wsdl-tsclient/issues/18
Temporally I created function which detects if propname contains weird characters, if so, it'll wrap it to double quotes.
const incorrectPropNameChars = [" ", "-", "."];
function sanitizePropName(propName: string) {
if (incorrectPropNameChars.some(char => propName.includes(char))) {
return `"${propName}"`;
}
return propName;
}
function createProperty(
name: string,
type: string,
doc: string,
isArray: boolean,
optional = true
): PropertySignatureStructure {
return {
kind: StructureKind.PropertySignature,
name: sanitizePropName(name),
docs: [doc],
hasQuestionToken: true,
type: isArray ? `Array<${type}>` : type,
};
}
Yeah, the solution isn't the best, but it will work most time. Do you have any plans to implement it right into ts-morph ?
Yeah, I ran into the same issue, and I was hoping to be able to at least use a writer function to properly quote the value, but PropertyNamedNodeStructure is only strings, not string | WriterFunction. I'll be submitting a PR momentarialy to allow name on PropertyNamedNodeStructure and PropertyNameableNodeStructure to be writers, so the following would work:
export function propertyKey(value: string): string | WriterFunction {
if (/^\w+$/.test(value)) {
return value;
}
// return JSON.stringify(value); // my current workaround
return (writer) => writer.quote(value);
}
(and use propertyKey like sanitizePropName above)
Hmm, I found the following for enums - should this be applied to all properties? or allow explicit writers? or both?
https://github.com/dsherret/ts-morph/blob/cea07aa7759ecf5a1e9f90b628334b8bd617c624/packages/ts-morph/src/structurePrinters/enum/EnumMemberStructurePrinter.ts#L29-L34
as, with both this would change to
// Adds quotes if structure is not a valid variable name
// AND the string is not enclosed in quotation marks
if (structure.name instanceof Function)
structure.name(writer)
else if (isValidVariableName(structure.name) || StringUtils.isQuoted(structure.name))
writer.write(structure.name);
else
writer.quote(structure.name);
Re-opened by https://github.com/dsherret/ts-morph/commit/055bf2158539d0b32c71141ca7c5e5213b85a611
I completely forgot about computed property names and for some reason there wasn't a test for that.