ohmjs/ohm

For namespaces, the generated bundle type definitions doesn't declare exactly what the generated module exposes

ericmorand opened this issue · 1 comments

When using the CLI command generateBundles, the generated type definitions are not exectaly in-sync with the generated JavaScript module.

From the following grammar:

Foo {
}
Bar {
}

Here is what the generated JS module looks like, formatted for clarity:

'use strict';
const ohm = require('ohm-js');
const result = ohm.createNamespace();
result.Foo = ohm.makeRecipe(["grammar", {"source": "Foo {\n}"}, "Foo", null, null, {}]);
result.Bar = ohm.makeRecipe(["grammar", {"source": "Bar {\n}"}, "Bar", null, null, {}]);
module.exports = result;

Here, the module is an object with two properties, Foo and Bar. Said differently, the JavaScript module consists of two named properties.

But here is what the generated types definition looks like:

// AUTOGENERATED FILE
// This file was generated from grammar.ohm by `ohm generateBundles`.

import {
  ActionDict,
  Grammar,
  IterationNode,
  Namespace,
  Node,
  NonterminalNode,
  Semantics,
  TerminalNode
} from 'ohm-js';

export interface FooActionDict<T> extends ActionDict<T> {
  
}

export interface FooSemantics extends Semantics {
  addOperation<T>(name: string, actionDict: FooActionDict<T>): this;
  extendOperation<T>(name: string, actionDict: FooActionDict<T>): this;
  addAttribute<T>(name: string, actionDict: FooActionDict<T>): this;
  extendAttribute<T>(name: string, actionDict: FooActionDict<T>): this;
}

export interface FooGrammar extends Grammar {
  createSemantics(): FooSemantics;
  extendSemantics(superSemantics: FooSemantics): FooSemantics;
}

export interface BarActionDict<T> extends ActionDict<T> {
  
}

export interface BarSemantics extends Semantics {
  addOperation<T>(name: string, actionDict: BarActionDict<T>): this;
  extendOperation<T>(name: string, actionDict: BarActionDict<T>): this;
  addAttribute<T>(name: string, actionDict: BarActionDict<T>): this;
  extendAttribute<T>(name: string, actionDict: BarActionDict<T>): this;
}

export interface BarGrammar extends Grammar {
  createSemantics(): BarSemantics;
  extendSemantics(superSemantics: BarSemantics): BarSemantics;
}

declare const ns: {
  Foo: FooGrammar;
  Bar: BarGrammar;
};
export default ns;

This type definitions declare that the generated JavaScript module exports a default object, ns. Hence it declares that the generated module doesn't export named properties.

This enforces all the consuming applications to be compiled with the esModuleInterop TypeScript directive...or to post-process the type definitions to append the legit named properties declarations.

Here, the module is an object with two properties, Foo and Bar. Said differently, the JavaScript module consists of two named properties.

Hmmm, I'm not sure I'd put it that way. The module has a single export, which is a Namespace object. That object has two properties.

The way it's declared was based on the recommendation from the TypeScript documentation on .d.ts files. I didn't realize that it requires the use of esModuleInterop, but I see from the documentation that you're right about that.

I could see a few possible ways forward:

  • The documentation mentions using the old export = syntax. Would it be preferable for us to use that instead?
  • Maybe there is little value in Namespace objects, and it would be simpler to just use regular, named exports.