#How to handle no-unsafe-declaration-merging without violating the DRY principle?

1 messages · Page 1 of 1 (latest)

lusty mason
#

When trying to upgrade from ESLint 4.x to ESLint 7.x, it’s no longer allowed to merge interfaces and classes and so I get the linting error Unsafe declaration merging between classes and interfaces.
This is the current implementation with the error:

interface ClassNode {
  'outputclass'?: string
  'class'?: string
}

interface LocalizationNode {
  'dir'?: string
  'xml:lang'?: string
  'translate'?: string
}

interface AltNode extends LocalizationNode, ClassNode {
  'text': string
}

abstract class BaseNode { }

class AltNode extends BaseNode { }

Renaming the interfaces by themselves so that their names do not clash with the class names is not enough, as the following code:

interface ClassNodeAttributes {
  'outputclass'?: string
  'class'?: string
}

interface LocalizationNodeAttributes {
  'dir'?: string
  'xml:lang'?: string
  'translate'?: string
}

interface AltNodeAttributes extends LocalizationNodeAttributes, ClassNodeAttributes {
  'text': string
}

abstract class BaseNode { }

class AltNode extends BaseNode implements AltNodeAttributes { }

Now I get following error message:

2420: Class 'AltNode' incorrectly implements interface 'AltNodeAttributes'.
  Property ''text'' is missing in type 'BaseNode' but required in type 'AltNodeAttributes'.

It seems that class AltNode is incorrectly implementing interface AltNodeAttributes, because property text is missing in type AltNode
One solution to fixing the compilation errors appears to be for me to duplicate all of the attributes from the interfaces into the class, for example:

class AltNode extends BaseNode implements AltNodeAttributes {
  'outputclass'?: string
  'class'?: string
  'dir'?: string
  'xml:lang'?: string
  'translate'?: string
  'text': string
}

However this approach doesn't seem optimal to me as it violates the DRY principle because I have had to define all of the attributes again, and additionally they are no longer gathered from their interfaces.

hollow herald
#

👋

#

so my first question is: does it have to be a class? i think you might be able to DRY things up by using objects + functions instead

lusty mason
#

I have a fairly large code base that models XML to typescript and we ve used classes, So moving to objects wont be an easy switch, nor viable as i m doing a lot of type checks against these classes.

hollow herald
#

okay, then my second idea would involve leaning more into classes. do you need to be able to mix together these interfaces in arbitrary ways? or do your types form a strict hierarchy that you could realize as just a bunch of classes that extend eachother?

lusty mason
#

we relied on declaration merging as a way to facilitate getting context for class properties per eg:

interface LocalizationNode {
  'dir'?: string
  'xml:lang'?: string
  'translate'?: string
}

I can tell that dir and translate are localization attributes and they come from LocalizationNode .

#

I didn't rely much on a hierarchy, I only have one abstract base class that hosts some common functionality, and I m using interfaces to keep track of attributes.

hollow herald
#

okay, then my third idea is basically "composition > inheritance". you probably won't like it because it also probably won't be an easy switch, but you could move specific kinds of properties to their own sub-objects:

formal matrixBOT
#
mkantor#0

Preview:ts ... class AltNode extends BaseNode { attributes?: AltNodeAttributes // hopefully actually required and set by the constructor }

lusty mason
#

I like your suggestion, actually it wont be that big of a switch compared to having to repeat my self and breaking DRY.
Thank you so much @hollow herald .