diff --git a/src/compiler/config/validate-config.ts b/src/compiler/config/validate-config.ts index 18e3a949b81..77bd76135f5 100644 --- a/src/compiler/config/validate-config.ts +++ b/src/compiler/config/validate-config.ts @@ -131,6 +131,7 @@ export const validateConfig = ( hydratedFlag: validateHydrated(config), logLevel, logger, + noDashCaseTypes: config.noDashCaseTypes ?? false, minifyCss: config.minifyCss ?? !devMode, minifyJs: config.minifyJs ?? !devMode, outputTargets: config.outputTargets ?? [], diff --git a/src/compiler/types/generate-app-types.ts b/src/compiler/types/generate-app-types.ts index 09643f214ee..d5ca85978d8 100644 --- a/src/compiler/types/generate-app-types.ts +++ b/src/compiler/types/generate-app-types.ts @@ -89,7 +89,7 @@ const generateComponentTypesFile = ( */ componentEventDetailTypes.push(generateEventDetailTypes(cmp)); } - return generateComponentTypes(cmp, typeImportData, areTypesInternal); + return generateComponentTypes(config, cmp, typeImportData, areTypesInternal); }); c.push(COMPONENTS_DTS_HEADER); diff --git a/src/compiler/types/generate-component-types.ts b/src/compiler/types/generate-component-types.ts index 30bfc96f5ec..1ca88f46c0e 100644 --- a/src/compiler/types/generate-component-types.ts +++ b/src/compiler/types/generate-component-types.ts @@ -8,12 +8,14 @@ import { generatePropTypes } from './generate-prop-types'; /** * Generate a string based on the types that are defined within a component + * @param config the validated config for the Stencil project * @param cmp the metadata for the component that a type definition string is generated for * @param typeImportData locally/imported/globally used type names, which may be used to prevent naming collisions * @param areTypesInternal `true` if types being generated are for a project's internal purposes, `false` otherwise * @returns the generated types string alongside additional metadata */ export const generateComponentTypes = ( + config: d.ValidatedConfig, cmp: d.ComponentCompilerMeta, typeImportData: d.TypesImportData, areTypesInternal: boolean, @@ -21,6 +23,7 @@ export const generateComponentTypes = ( const tagName = cmp.tagName.toLowerCase(); const tagNameAsPascal = dashToPascalCase(tagName); const htmlElementName = `HTML${tagNameAsPascal}Element`; + const noDashCaseTypes = config.noDashCaseTypes; const propAttributes = generatePropTypes(cmp, typeImportData); const methodAttributes = generateMethodTypes(cmp, typeImportData); @@ -31,9 +34,15 @@ export const generateComponentTypes = ( [...propAttributes, ...methodAttributes], false, areTypesInternal, + noDashCaseTypes, ); const isDep = cmp.isCollectionDependency; - const jsxAttributes = attributesToMultiLineString([...propAttributes, ...eventAttributes], true, areTypesInternal); + const jsxAttributes = attributesToMultiLineString( + [...propAttributes, ...eventAttributes], + true, + areTypesInternal, + noDashCaseTypes, + ); const element = [ ...htmlElementEventMap, @@ -60,7 +69,12 @@ export const generateComponentTypes = ( }; }; -const attributesToMultiLineString = (attributes: d.TypeInfo, jsxAttributes: boolean, internal: boolean) => { +const attributesToMultiLineString = ( + attributes: d.TypeInfo, + jsxAttributes: boolean, + internal: boolean, + noDashCaseTypes: boolean, +) => { const attributesStr = sortBy(attributes, (a) => a.name) .filter((type) => { if (jsxAttributes && !internal && type.internal) { @@ -76,20 +90,21 @@ const attributesToMultiLineString = (attributes: d.TypeInfo, jsxAttributes: bool } const optional = jsxAttributes ? !type.required : type.optional; fullList.push(` "${type.name}"${optional ? '?' : ''}: ${type.type};`); - - /** - * deprecated usage of dash-casing in JSX, use camelCase instead - */ - if (type.attributeName && type.attributeName !== type.name) { - const padding = ' '.repeat(8); - fullList.push( - [ - `${padding}/**`, - `${padding} * @deprecated use camelCase instead. Support for dash-casing will be removed in Stencil v5.`, - `${padding} */`, - ].join('\n'), - ); - fullList.push(`${padding}"${type.attributeName}"?: ${type.type};`); + if (noDashCaseTypes === false) { + /** + * deprecated usage of dash-casing in JSX, use camelCase instead + */ + if (type.attributeName && type.attributeName !== type.name) { + const padding = ' '.repeat(8); + fullList.push( + [ + `${padding}/**`, + `${padding} * @deprecated use camelCase instead. Support for dash-casing will be removed in Stencil v5.`, + `${padding} */`, + ].join('\n'), + ); + fullList.push(`${padding}"${type.attributeName}"?: ${type.type};`); + } } return fullList; diff --git a/src/compiler/types/tests/generate-app-types.spec.ts b/src/compiler/types/tests/generate-app-types.spec.ts index 71a66c2c22d..968c9505360 100644 --- a/src/compiler/types/tests/generate-app-types.spec.ts +++ b/src/compiler/types/tests/generate-app-types.spec.ts @@ -1488,6 +1488,95 @@ declare module "@stencil/core" { } } } +`, + { + immediateWrite: true, + }, + ); + }); + + it('should not generate dash-case types if noDashCaseTypes is set to true', async () => { + const compilerComponentMeta = stubComponentCompilerMeta({ + tagName: 'my-component', + componentClassName: 'MyComponent', + hasProp: true, + properties: [ + stubComponentCompilerProperty({ + name: 'name', + complexType: { + original: 'UserImplementedPropType', + resolved: '"foo" | "bar"', + references: { + UserImplementedPropType: { + location: 'import', + path: './resources', + id: './resources::UserImplementedPropType', + }, + }, + }, + }), + ], + }); + buildCtx.components = [compilerComponentMeta]; + config.noDashCaseTypes = true; + + await generateAppTypes(config, compilerCtx, buildCtx, 'src'); + + expect(mockWriteFile).toHaveBeenCalledWith( + '/components.d.ts', + `/* eslint-disable */ +/* tslint:disable */ +/** + * This is an autogenerated file created by the Stencil compiler. + * It contains typing information for all components that exist in this project. + */ +import { HTMLStencilElement, JSXBase } from "@stencil/core/internal"; +import { UserImplementedPropType } from "./some/stubbed/path/resources"; +export { UserImplementedPropType } from "./some/stubbed/path/resources"; +export namespace Components { + /** + * docs + */ + interface MyComponent { + "name": UserImplementedPropType; + } +} +declare global { + /** + * docs + */ + interface HTMLMyComponentElement extends Components.MyComponent, HTMLStencilElement { + } + var HTMLMyComponentElement: { + prototype: HTMLMyComponentElement; + new (): HTMLMyComponentElement; + }; + interface HTMLElementTagNameMap { + "my-component": HTMLMyComponentElement; + } +} +declare namespace LocalJSX { + /** + * docs + */ + interface MyComponent { + "name"?: UserImplementedPropType; + } + interface IntrinsicElements { + "my-component": MyComponent; + } +} +export { LocalJSX as JSX }; +declare module "@stencil/core" { + export namespace JSX { + interface IntrinsicElements { + /** + * docs + */ + "my-component": LocalJSX.MyComponent & JSXBase.HTMLAttributes; + } + } +} `, { immediateWrite: true, diff --git a/src/declarations/stencil-public-compiler.ts b/src/declarations/stencil-public-compiler.ts index 36e5bf6ac6b..563b40c0b36 100644 --- a/src/declarations/stencil-public-compiler.ts +++ b/src/declarations/stencil-public-compiler.ts @@ -309,6 +309,11 @@ export interface StencilConfig { */ excludeUnusedDependencies?: boolean; stencilCoreResolvedId?: string; + /** + * Sets whether Stencil will generate dash-cased types (with deprecated comment) + * in `components.d.ts`. Defaults to `false` + */ + noDashCaseTypes?: boolean; } interface ConfigExtrasBase { @@ -501,6 +506,7 @@ type StrictConfigFields = keyof Pick< | 'minifyCss' | 'minifyJs' | 'namespace' + | 'noDashCaseTypes' | 'outputTargets' | 'packageJsonFilePath' | 'rollupConfig' diff --git a/src/testing/mocks.ts b/src/testing/mocks.ts index d4e8e0ce684..7ed76ed22ce 100644 --- a/src/testing/mocks.ts +++ b/src/testing/mocks.ts @@ -40,6 +40,7 @@ export function mockValidatedConfig(overrides: Partial = {}): minifyCss: false, minifyJs: false, namespace: 'Testing', + noDashCaseTypes: false, outputTargets: baseConfig.outputTargets ?? [], packageJsonFilePath: path.join(rootDir, 'package.json'), rootDir,