microsoft / microsoft/TypeScript

Wildcard ambient modules declaration override rules

Đang mở
#32,422 1 bình luận 0 reaction 1 người được giao Xem trên GitHub

@rbuckton đang làm issue này rồi.

Từ ngày 26/7/2019.

Needs Investigation
Ngôn ngữ chính
Go
Star
111k
Fork
14.3k
Merge trung bình
2 ngày 4 giờ
Pull request đã merge (30 ngày)
132

Mô tả

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.

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Đánh giá

Issue này chưa được đánh giá.

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.