Ts-morph: Support automatically determining if a string literal should be written for a property key or value in Writers.object

Created on 18 May 2021  Â·  3Comments  Â·  Source: dsherret/ts-morph

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 
    }; 
enhancement

Most helpful comment

@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.

All 3 comments

@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 ?

Was this page helpful?
0 / 5 - 0 ratings