microsoft / microsoft/TypeScript

Wildcard ambient modules declaration override rules

Abierto
#32,422 1 comentario 0 reacciones 1 asignado Ver en GitHub

@rbuckton ya está trabajando en esto.

Desde el 26/7/2019.

Needs Investigation
Lenguaje dominante
Go
Estrellas
111k
Forks
14.3k
Merge medio
2 d 4 h
PR fusionados (30 d)
132

Descripción

Search Terms

wildcard ambient module override

Suggestion

I could not find written anywhere how wildcard ambient module declaration precedence work in case of overlaps.

In the original pull request "prefix length" was used as the best fit criteria, but I could not locate where that function is located into current code.
Also, prefix length won't help in case of post-fixed *, but maybe that was just naming and it actually meant "longer match".

This feature should be better documented into its handbook page or, if it has been dropped after first implementation, it would be useful to know why so.

The most closely related question I found on StackOverflow doesn't have an answer.

Use Cases

I have a setup with Vue (Quasar actually) + TypeScript + Jest, using SFC.
I have to mount Vue components into Jest tests (which are written into .ts files), but importing SFC won't work (they are not TS files).
Using a shim for all Vue files (the official solution) partially solves this problem, because at least you get typings for the general Vue instance, but you won't get typings for that particular SFC (data, props, computed, etc).

I currently separated the TS script from the SFC to be able to get the typings by importing from the two different files. Now I'm trying to define shims for the component .vue to work by binding its name with a wildcard to it's TS counterpart.

Unluckily, when I import ./demo/QBtn-demo.vue, I still get *.vue shim instead of the specific component one, and nowhere seems to be found how can I force the override.

If I remove Vue shim, it works, but I'm forced to make a personal shim for every component.

I know it's possible by using triple slash references, but that's not the point of this issue.

Current workaround is to import both the .vue SFC and the TS script and then explicitly cast the SFC to the type of the specific instance.

Examples

shims-vue.d.ts

declare module '*.vue' {
  import Vue from 'vue';
  export default Vue;
}

component.d.ts

declare module '*/QBtn-demo.vue' { // <= works when general Vue shim isn't present
  import QBtnDemo from 'test/jest/__tests__/demo/QBtn-demo'; // <= this is the TS file
  export default QBtnDemo;
}

QBtn-demo.vue

<script lang="ts" src="./QBtn-demo.ts"></script>

<template>
  <div>
    <p class="textContent">{{ input }}</p>
    <span>{{ counter }}</span>
    <q-btn id="mybutton" @click="increment()"></q-btn>
  </div>
</template>

QBtn-demo.ts

import Vue from 'vue';

export default Vue.extend({
  name: 'QBUTTON',
  data: function(): { counter: number; input: string } {
    return {
      counter: 0,
      input: 'rocket muffin',
    };
  },
  methods: {
    increment(): void {
      this.counter++;
    },
  },
});

app,spec.ts

import { createLocalVue, mount } from '@vue/test-utils';
import { Quasar } from 'quasar';
import { VueConstructor } from 'vue';

import QBtnDemo from './demo/QBtn-demo.vue'; // <= Gets types as 'Vue' instead of 'QBtnDemo'

describe('Mount Quasar', () => {
  const localVue = createLocalVue();
  localVue.use(Quasar);

  const wrapper = mount(QBtnDemo, { localVue });
  const vm = wrapper.vm;

  it('has a created hook', () => {
    expect(typeof vm.increment).toBe('function'); // <= TS error: could not find 'increment'
  });

  it('sets the correct default data', () => {
    expect(typeof vm.counter).toBe('number');  // <= TS error: could not find 'counter'
    const defaultData = vm.$data;
    expect(defaultData.counter).toBe(0);
  });

  it('correctly updates data when button is pressed', () => {
    const button = wrapper.find('button');
    button.trigger('click');
    expect(vm.counter).toBe(1);  // <= TS error: could not find 'counter'
  });
});

app.spec.ts with casting workaround

import { createLocalVue, mount } from '@vue/test-utils';
import { Quasar } from 'quasar';
import { VueConstructor } from 'vue';

import QBtnDemoComponent from './demo/QBtn-demo.vue';
import QBtnDemo from './demo/QBtn-demo';

describe('Mount Quasar', () => {
  const localVue = createLocalVue();
  localVue.use(Quasar);

  const wrapper = mount(QBtnDemoComponent as typeof QBtnDemo, { localVue });
  const vm = wrapper.vm;

  it('has a created hook', () => {
    expect(typeof vm.increment).toBe('function'); // <= Infered correctly
  });

  it('sets the correct default data', () => {
    expect(typeof vm.counter).toBe('number'); // <= Infered correctly
    const defaultData = vm.$data;
    expect(defaultData.counter).toBe(0);
  });

  it('correctly updates data when button is pressed', () => {
    const button = wrapper.find('button');
    button.trigger('click');
    expect(vm.counter).toBe(1); // <= Infered correctly
  });
});

Checklist

My suggestion meets these guidelines:

  • This wouldn't be a breaking change in existing TypeScript/JavaScript code
  • This wouldn't change the runtime behavior of existing JavaScript code
  • This could be implemented without emitting different JS based on the types of the expressions
  • This isn't a runtime feature (e.g. library functionality, non-ECMAScript syntax with JavaScript output, etc.)
  • This feature would agree with the rest of TypeScript's Design Goals.

Guía de contribución

Abrir la guía de contribución

Primeros pasos

  1. Lee el issue completo y luego la guía de contribución del proyecto.
  2. Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
  3. Haz un fork del repositorio y trabaja en una rama.
  4. Abre un pull request que haga referencia al número del issue.

Evaluación

Este issue todavía no se ha evaluado.

Recibe los nuevos issues en tu correo

Un resumen breve de issues de GitHub para principiantes.