KRyan
KRyan

Reputation: 7618

What is the default “tag” function for template literals?

What is the name of the native function that handles template literals?

That is, I know that when you write tag`Foo ${'bar'}.`;, that’s just syntactic sugar for tag(['Foo ', '.'], 'bar');

But what about just ​`Foo ${'bar'}.`;? I can’t just “call” (['Foo ', '.'], 'bar');. If I already have arguments in that form, what function should I pass them to?

I am only interested in the native function that implements the template literal functionality. I am quite capable of rolling my own, but the purpose of this question is to avoid that and do it “properly”—even if my implementation is a perfect match of current native functionality, the native functionality can change and I want my usage to still match. So answers to this question should take on one of the following forms:

  1. The name of the native function to use, ideally with links to and/or quotes from documentation of it.

  2. Links to and/or quotes from the spec that defines precisely what the implementation of this function is, so that if I roll my own at least I can be sure it’s up to the (current) specifications.

  3. A backed-up statement that the native implementation is unavailable and unspecified. Ideally this is backed up by, again, links to and/or quotes from documentation, but if that’s unavailable, I’ll accept other sources or argumentation that backs this claim up.


  1. Actually, the first argument needs a raw property, since it’s a TemplateStringsArray rather than a regular array, but I’m skipping that here for the sake of making the example more readable.

Motivation

I am trying to create a tag function (tag, say) that, internally, performs the default template literal concatenation on the input. That is, I am taking the TemplateStringsArray and the remaining arguments, and turning them into a single string that has already had its templating sorted out. (This is for passing the result into another tag function, otherTag perhaps, where I want the second function to treat everything as a single string literal rather than a broken up template.)

For example, tag`Something ${'cooked'}.`; would be equivalent to otherTag`Something cooked.`;.

My current approach

The definition of tag would look something like this:

function tag(textParts, ...expressions) {
  const cooked = // an array with a single string value
  const raw = // an array with a single string value
  return otherTag({ ...cooked, raw });
}

Defining the value of raw is fairly straightforward: I know that String.raw is the tag function I need to call here, so const raw = [String.raw(textParts.raw, ...expressions)];.

But I cannot find anywhere on the internet what function I would call for the cooked part of it. What I want is, if I have tag`Something ${'cooked'}.`;, I want const cooked = `Something ${cooked}.`; in my function. But I can’t find the name of whatever function accomplishes that.

The closest I’ve found was a claim that it could be implemented as

const cooked = [expressions.map((exp, i) => textParts[i] + exp).join('')];

This is wrong—textParts may be longer than expressions, since tag`Something ${'cooked'}.`; gets ['Something ', '.'] and ['cooked'] as its arguments.

Improving this expression to handle that isn’t a problem:

const cooked = [
  textParts
    .map((text, i) => (i > 0 ? expressions[i-1] : '') + text)
    .join(''),
];

But that’s not the point—I don’t want to roll my own here and risk it being inconsistent with the native implementation, particularly if that changes.

Upvotes: 5

Views: 943

Answers (2)

Sam Becker
Sam Becker

Reputation: 19646

This implementation seems to match the behaviour of the built-in syntax pretty well. The test case throws a lot of nonsense at both implementations and it seems to pass:

function example<TValues extends unknown[]>(
  parts: TemplateStringsArray,
  ...values: TValues
): string {
  return parts
    .flatMap((part, i) =>
      i < values.length ? [part, String(values[i])] : [part],
    )
    .join('');
}

describe('example', () => {
  it('produces a string that matches the behaviour of the built in version', () => {
    expect(
      example`a complex * ${'string'} with some funky stuff going on\n\r\s
      \${}${1}${2}${'3'},${{ foo: 'bar' }},${['biz', 1, {}]}${null}${undefined}
      $$1$1$24Ω≈ç√∫˜µ≤≥÷`,
    ).toEqual(
      `a complex * ${'string'} with some funky stuff going on\n\r\s
      \${}${1}${2}${'3'},${{ foo: 'bar' }},${['biz', 1, {}]}${null}${undefined}
      $$1$1$24Ω≈ç√∫˜µ≤≥÷`,
    );
  });
});

Upvotes: 1

Quentin
Quentin

Reputation: 944186

The name of the native function to use, ideally with links to and/or quotes from documentation of it.

There isn't one. It is syntax, not a function.

Links to and/or quotes from the spec that defines precisely what the implementation of this function is, so that if I roll my own at least I can be sure it’s up to the (current) specifications.

Section 13.2.8 Template Literals of the specification explains how to process the syntax.

Upvotes: -1

Related Questions