Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: Remove I-prefix from TypeScript interfaces and types #10266

Closed
dzearing opened this issue Aug 26, 2019 · 9 comments
Closed

RFC: Remove I-prefix from TypeScript interfaces and types #10266

dzearing opened this issue Aug 26, 2019 · 9 comments
Assignees
Labels
Needs: Discussion Resolution: Soft Close Soft closing inactive issues over a certain period

Comments

@dzearing
Copy link
Member

dzearing commented Aug 26, 2019

Overview

All existing interfaces and types in all v7 packages are currently prefixed with I. All interfaces and types in v0 packages are not. Let's choose one path.

Reasons for prefixing

  1. Seeing an I prefix gives the developer ease of mind that adding the graph edge will not impact bundle size.
  2. Avoids common naming collisions. E.g. the interface for an IThing could be implemented by a Thing. Without the prefix, you would need an alternative prefix or suffix to avoid the collision such as ThingAPI or ThingInterface, which leads to inconsistencies.

If we choose to embrace I-prefix, we can avoid naming collisions but will need to cov

Reasons against prefixing

  1. Consistency with the community. TypeScript coding conventions suggest avoiding. Most OSS TypeScript projects seem to follow this guidance and avoid prefixing.
  2. Tools like bundle-size can tell you fairly soon if an import has cost or not (but only on a per import level and not on a per type.)
  3. TypeScript “type” definitions aren’t technically interfaces so prefixing with the letter I feels weird.
  4. Linting defaults to avoiding.
  5. Interfaces technically can be replaced with classes and still be implmeented.

If we choose to abandon I-prefix, we need to propose naming guidance for when developers hit the classic IFoo interface vs Foo implementation.

Prior considerations

TypeScript thread here:

microsoft/TypeScript-Handbook#121

There are multiple customer complaints that the guidance has no principles or reasoning behind the decision other than it was deemed "an unnecessary relic of C# days". No guidance been provided in regards to naming conventions to deal with typing differentiation. Additionally the TypeScript team has mentioned not prefixing generic placeholders with T (e.g. function foo<TThing>(arg: TThing)).

Stack overflow article here:

https://stackoverflow.com/questions/31876947/confused-about-the-interface-and-class-coding-guidelines-for-typescript

Some clarifications offered:

  • Don't have an IFoo type and a Foo implementation. That's "bad" because Foo should be more specific.
  • Prefixing interfaces with I violates encapsulation of the type (changing from an interface to a type would then remove the I right?) Our approach has been to prefix all type and interface names with I, and treat them interchangeably.

Proposed direction

For consistency with TypeScript conventions, we abandon I-prefix. This means that all interfaces would need to be renamed, with I-prefixed deprecated re-exports.

Example: IColor interface, or ISize type becomes:

export interface Color { ... }
export type Size = 'foo' | 'bar';

/** @deprecated - `IColor` has been deprecated, use the `Color` interface instead. **/
export interface IColor extends Color {}

/** @deprecated - `ISize` has been deprecated; use the `Size` type instead. **/
export type ISize = Size;

Once we settle, we will update the wiki here:
https://github.com/microsoft/fluentui/wiki/Coding-Style#prefix-interfaces-and-sometimes-types-with-i

Special considerations

There are many scenarios where removing the prefix creates a symbol collision. Naming is hard, and removing the prefix makes things harder, so we need to have strong guidance on improved type naming conventions to reduce inconsistencies.

Collision scenario Example of conflict Proposed replacement
componentRef interfaces IButton Append ImperativeHandle suffix. (ButtonImperativeHandle)
fallback scenarios IButtonStyles Append Type suffix. (ButtonStylesType)

More examples of collisions in the codebase currently:

IExampleGroup, IRefObject, IRectangle, IAppCustomizations, IColorClassNames, IAnimationStyles, IAnimationVariables, ICustomizations, ISettings, ISettingsFunction, ICustomizerContext, ISelection, IDonutChart, ILineChart, IPieChart, IStackedBarChart, IVerticalBarChart, IDatePickerStyles, IAppDefinition, IAccordion, IButtonSlots, IButtonTokens, IButtonStyles, IChicletCard, ICollapsibleSection, IFloatingSuggestions, IMicroFeedback, IMicroFeedbackTokens, IMicroFeedbackStyles, IPersonaCoinStyles, ISelectedItemsList, ISliderStyles, IToggleTokens, IToggleStyles, IScrollContainer, IBreadcrumbStyles, IButtonStyles, ICalloutContentStyles, ICheckStyles, ICheckboxStyles, IColorPickerStyles, IComboBoxStyles, ICommandBarStyles, IContextualMenuItem, IContextualMenuStyles, IDatePickerStyles, IDetailsColumnStyles, IDetailsHeaderStyles, IDetailsListStyles, IDetailsRowStyles, IDetailsRowCheckStyles, IDialogContentStyles, IDialogFooterStyles, IDocumentCardStyles, IDropdown, IDropdownStyles, IBaseExtendedPicker, IFacepileStyles, IGroupHeaderStyles, IExpandingCard, IExpandingCardStyles, IImage, IKeytip, ILabelStyles, ILinkStyles, IPage, IMessageBarStyles, IModalStyles, INavStyles, IOverlayStyles, IPanelStyles, IPersonaStyles, IPersonaCoinStyles, IBasePicker, IBasePickerStyles, IPivotStyles, IProgressIndicatorStyles, IRatingStyles, IScrollablePaneContext, ISearchBoxStyles, IBaseSelectedItemsList, ISeparator, ISliderStyles, ISpinButtonStyles, IColorPickerGridCellStyles, ITeachingBubbleStyles, ITextStyles, ITextFieldStyles, IToggleStyles, IViewport, IDragDropHelper, IPosition, ISelectionZone, ICard, ICardTokens, ICardStyles, IActionableSlots, IActionable, IActionableTokens, IActionableStyles, IMenuButtonSlots, IMenuButton, IMenuButtonTokens, IMenuButtonStyles, ISplitButtonSlots, ISplitButton, ISplitButtonTokens, ISplitButtonStyles, IVerticalPersonaStyles, IVerticalPersonaTokens, ISelectedPeopleList, IExampleGroup, IChoiceGroupOptionStyles, IColorRectangleStyles, IColorSliderStyles, IPlainCard, IPlainCardStyles, ISuggestions, ISuggestionsStyles, ITagItemStyles, IStackItemStyles, ICardItem, ICardItemTokens, ICardItemStyles, ICardSection, ICardSectionTokens, ICardSectionStyles

Conversion plan for codebase

A ts-morph script would manage the renames in bulk. The process would be that for each interface identified that matches an I prefix, apply a rename across the repo. This applies to both interface and type definitions. The old name would also be exported as deprecated to avoid breaking changes:

Before:

export interface IButton { ... }
export type IButtonSize = "small" | "medium";
export interface IButtonProps {
  componentRef?: RefObject<IButton>;
  size?: IButtonSize;
}

After:

export interface ButtonRef { ... }
export type ButtonSize = "small" | "medium";
export interface ButtonProps {
  componentRef?: RefObject<ButtonRef>;
  size?: ButtonSize;
}

/** @deprecated - Use `ButtonRef` instead. */
export interface IButtonRef extends ButtonRef {}

/** @deprecated - Use `ButtonSize` instead. */
export type IButtonSize = ButtonSize;

/** @deprecated - Use `ButtonProps` instead. */
export interface IButtonProps extends ButtonProps {}

Conversion plan for customers

An upgrade-fluentui command would be provided for customers as an executable node script upon install. This script would apply all symbolic renames defined in a JSON file to the customer's source files when executed.

The JSON file would define all symbolic renames resulting from the automated conversion above.

When the user runs upgrade-fluentui in their repo, these changes would be applied, as an example:

Before (src/**/App.tsx)

import { Button, IButtonProps } from '@fluentui/react';

const buttonProps: IButtonProps = {
  size: 'small'
};

const App = () => (
  <Button {...buttonProps } />
);

After:

import { Button, ButtonProps } from '@fluentui/react';

const buttonProps: ButtonProps = {
  size: 'small'
};

const App = () => (
  <Button {...buttonProps } />
);

This process would need to be documented, and ideally mentioned when yarn installing, similar to how some npm libraries have postinstall steps that warn about out of date dependencies.

@jdhuntington
Copy link
Contributor

jdhuntington commented Aug 26, 2019

I am in favor of omitting the I prefix primarily for reasons 1 and 2 listed above.

@KevinTCoughlin
Copy link
Member

I don't mind the I prefix, but do see reason 1 as compelling. What is most important to me is that we are consistent and do not break partners which you've already called out below.

If we indeed decide to abandon the pattern, we could still export both to prevent breakage.

@micahgodbolt
Copy link
Member

micahgodbolt commented Aug 26, 2019

number 3 is pretty valid as well. If we already have tools informing us of the cost of importing, we are making a major deviation from the OSS/TS standard for little reason.

@cliffkoh
Copy link
Contributor

cliffkoh commented Aug 30, 2019

I still keep I to be consistent with OUFR but drop I in all type aliases (very simply because type aliases are not interfaces).

I'm not sure if it is worth the massive churn (including downstream) to change at this point. We could possibly export both but then that raises a 2nd question: do we then always export two versions for all new interfaces as well?

@dzearing
Copy link
Member Author

Just coming back to this. I tried not using I prefixes and run into naming concerns a bunch.

For example, we have Button component, and IButton imperative interface. We need a new convention for imperative interfaces. ButtonAPI ?

To me I keep thinking of the prefix as a private indicator. It expresses intent; don't use this thing as a new'able resource. It is the type safety that typescript will strip out in a transpile step.

But I think i'm swimming upstream here.

@dzearing dzearing changed the title Should interfaces be prefixed with “I”? RFC: TypeScript interfaces and types prefixing Apr 7, 2020
@dzearing dzearing changed the title RFC: TypeScript interfaces and types prefixing RFC: Remove I-prefix from TypeScript interfaces and types Apr 8, 2020
@khmakoto
Copy link
Member

khmakoto commented Apr 9, 2020

I'm indifferent towards one approach or the other but definitely see the benefit of having consistent naming standards in our codebase. If we do decide to drop the I before interfaces and go with deprecated names I think this is as good a time as any to start thinking how long we're going to support deprecations. Will we delete deprecated props in the next release? Will we support these props in the next release and delete them the release after that? If we go with the second option, how do we differentiate props we deprecated in v-1 that we need to support vs props we deprecated in v-2 that we need to delete?

@dzearing
Copy link
Member Author

dzearing commented Apr 9, 2020

@khmakoto I would propose that given there is an upgrade script for customers, and in my proposal a JSON file documenting all symbolic renames, we could use the JSON list as a "safe for removal" list on upgrade.

As long as customers have a way to easily update their code, I think we can be more aggressive about cleanup. It's when it's a giant chore for them that we need to reserve major caution.

That's my take, what do you think?

@khmakoto
Copy link
Member

khmakoto commented Apr 9, 2020

@dzearing that sounds good to me, I think going forward having the least baggage possible is going to be a good thing. 😄

@msft-fluent-ui-bot
Copy link
Collaborator

Because this issue has not had activity for over 150 days, we're automatically closing it for house-keeping purposes.

Still require assistance? Please, create a new issue with up-to date details.

@msft-fluent-ui-bot msft-fluent-ui-bot added the Resolution: Soft Close Soft closing inactive issues over a certain period label Apr 26, 2021
@microsoft microsoft locked as resolved and limited conversation to collaborators May 27, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Needs: Discussion Resolution: Soft Close Soft closing inactive issues over a certain period
Projects
None yet
Development

No branches or pull requests

9 participants