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

[WIP] Clean up version of using overload for JSX stateless function component #12107

Closed
wants to merge 25 commits into from

Conversation

yuit
Copy link
Contributor

@yuit yuit commented Nov 8, 2016

@sandersn Thanks for reviewing the original PR! This one should make life much easier

@mhegazy @RyanCavanaugh

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments and questions on the checker. I'll look at the other files next.

const paramType = getTypeAtPosition(signature, 0);

// JSX opening-like element has correct arity for stateless-function component if the one of the following condition is true:
// 1. callIsInCompletes
Copy link
Member

@sandersn sandersn Nov 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo:callIsIncomplete #Closed

// We can figure that out by resolving attributes property and check number of properties in the resolved type
// If the call has correct arity, we will then check if the argument type and parameter type is assignable

const callIsIncomplete = node.attributes.end === node.end; // If we are missing the close "/>", the call is incomplete
Copy link
Member

@sandersn sandersn Nov 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like you should move if (callIsIncomplete) right below here to avoid the checkExpression call when possible. #Closed

let shouldCheckArgumentAndParameter = true;
for (const arg of argProperties) {
if (!getPropertyOfType(paramType, arg.name) && isUnhyphenatedJsxName(arg.name)) {
shouldCheckArgumentAndParameter = false;
Copy link
Member

@sandersn sandersn Nov 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can't you just return false here and get rid of the boolean flag? #Closed

break;
}
}
if (shouldCheckArgumentAndParameter && checkTypeRelatedTo(argType, paramType, relation, /*errorNode*/ undefined, headMessage)) {
Copy link
Member

@sandersn sandersn Nov 8, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this requirement that the types have the same set of properties kind of looks like compareTypesIdentical (except for hyphenated properties). But each property is still compared for normal assignability. This is kind of confusing. Why not just use one-directional assignability that checkTypeRelatedTo gives you? #Pending

@@ -12604,6 +12861,14 @@ namespace ts {
return result;
}

// Do not report any error if we are doing so for stateless function component as such error will be error will be handle in "resolveCustomJsxElementAttributesType".
Copy link
Member

@sandersn sandersn Nov 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

replace: "will be error will be handle" with "will be handled" #Closed

Copy link
Member

@sandersn sandersn Nov 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's suspicious to see error handling paths duplicated like this. Can you treat jsx stateless function components close enough to a normal call that you can use the same error paths? So far I have seen three obstacles:

  1. hypenated properties are treated differently
  2. The rules for applicability are (perhaps?) different.
  3. Error reporting on JSX as if it were a function call might be confusing.

Can you think of ways to handle these on the normal code paths instead of putting them in a set of alternate functions? Are there other problems? (3) doesn't seem that hard to fix locally. #Closed

Copy link
Contributor Author

@yuit yuit Nov 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason that JSX stateless function can't be treat as normal call because

  • To be valid stateless function, we have to check that the return type match JSXElementType (see :defaultTryGetJsxStatelessFunctionAttributesType) I don't want to move that check here since it is such JSX specific.
  • When trying to resolve JSX tag-name that is not intrinsic, we do following : try as if it is stateless, if fails then try as stateful. #Resolved

* @return a resolved signature if we can find function matching function signature through resolve call or a first signature in the list of functions.
* otherwise return undefined if tag-name of the opening-like element doesn't have call signatures
*/
function resolvedStateLessJsxOpeningLikeElement(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature {
Copy link
Member

@sandersn sandersn Nov 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More MicroSoft disease: StateLess → Stateless #Closed

Copy link
Member

@sandersn sandersn Nov 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also I think resolved should be resolve so that the first word is a verb #Closed

* @param elementType
* @param candidatesOutArray
*/
function getResolvedJSXStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature {
Copy link
Member

@sandersn sandersn Nov 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JSX → Jsx #Closed

if (elementType.flags & TypeFlags.Union) {
const types = (elementType as UnionType).types;
let result: Signature;
types.map(type => {
Copy link
Member

@sandersn sandersn Nov 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should just be for (const type of types) ? #Closed

function getResolvedJSXStatelessFunctionSignature(openingLikeElement: JsxOpeningLikeElement, elementType: Type, candidatesOutArray: Signature[]): Signature {
Debug.assert(!(elementType.flags & TypeFlags.Union));
const links = getNodeLinks(openingLikeElement);
// If getResolvedSignature has already been called, we will have cached the resolvedSignature.
Copy link
Member

@sandersn sandersn Nov 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code looks a lot like getResolvedSignature. Are you sure you can't re-use getResolvedSignature and move the core difference either down in to resolveStatelessJsxOpeningLikeElement or up into tryGetJsxStatelessFuncitonAttributesType? #Pending

let callSignature: Signature;
callSignature = resolveCall(openingLikeElement, callSignatures, candidatesOutArray);
return callSignature;
}
Copy link
Member

@sandersn sandersn Nov 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to fill up candidatesArray? Is it for the language service? Shouldn't normal resolveCall fill up candidatesArray for a union? #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the function resolveCall will fill up candidatesArray which is pass in from language service. I don't quite understand the second question XD - what union are you referring to?


In reply to: 87291084 [](ancestors = 87291084)

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments for rest of source. Looking at tests next.

@@ -988,6 +994,24 @@ namespace Harness {
}
}

if (libFiles) {
Copy link
Member

@sandersn sandersn Nov 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this needed as part of this PR or can it be broken into a separate change? #Closed

Copy link
Contributor Author

@yuit yuit Nov 15, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it kind of because otherwise test will be very slow since we will reparse lib file everytime
#Resolved

// attributes: JsxAttributes
// properties: NodeArray<JsxAttributeLike>
// each JsxAttribute can have initializer as JsxExpression
return /*JsxExpression*/parent./*JsxAttribute*/parent./*JsxAttributes*/parent.parent as JsxOpeningLikeElement;
Copy link
Member

@sandersn sandersn Nov 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the inline comments are a neat idea, but I think they're still too distracting for their value. It's probably good enough to have the tree comment preceding the line of code #Closed

return <JsxOpeningLikeElement>parent.parent.parent;
parent.parent && parent.parent.kind === SyntaxKind.JsxAttribute) {
// Currently we parse JsxOpeninLikeElement as:
// JsxOpeninLikeElement
Copy link
Member

@sandersn sandersn Nov 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JsxOpeningLikeElement is missing a 'g', everywhere in the comments of this file. #Closed

return forEach(typeChecker.getRootSymbols(contextualSymbol), s => searchSymbols.indexOf(s) >= 0 ? s : undefined);
});

if (contextualSymbol) {
return contextualSymbol;
}

// If the reference location is the name of property from object literal destructuring pattern
// If the reference location is the name of property from object literal destructing pattern
Copy link
Member

@sandersn sandersn Nov 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

original spelling was correct #Closed

// /*firstSource*/declare function MainButton(buttonProps: ButtonProps): JSX.Element;
// /*secondSource*/declare function MainButton(linkProps: LinkProps): JSX.Element;
// /*thirdSource*/declare function MainButton(props: ButtonProps | LinkProps): JSX.Element;
// let opt = <Main/*firstTarget*/Button />; // We get undefined for resolved signature indicating an error, then just return the first declaration
Copy link
Member

@sandersn sandersn Nov 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't it make more sense to return the last one since it's the most general? #Pending

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hmmm I was trying to mimic how normal call-expression does when there is an error. I swear I thought it goto the first declaration but I was wrong. I think then it should go to the last one


In reply to: 87302820 [](ancestors = 87302820)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

did you already change this to returning the last? I thought I saw the change but now I can't find it.


In reply to: 88111830 [](ancestors = 88111830,87302820)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change is done in checker resolveCall by returning "candidateForArgumentError" instead of undefined. Though I should remove this.


In reply to: 88706239 [](ancestors = 88706239,88111830,87302820)

if (contextualType) {
let result: DefinitionInfo[] = [];
forEach(getPropertySymbolsFromContextualType(typeChecker, container), contextualSymbol => {
result = result.concat(getDefinitionFromSymbol(typeChecker, contextualSymbol, node));
Copy link
Member

@sandersn sandersn Nov 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does this differ from for( ... of ... ) #Closed

Copy link
Member

@sandersn sandersn Nov 9, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't, as far as I can tell, so I would use for-of.

Copy link
Member

@sandersn sandersn Nov 10, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't this loop get every property of the contextual type, instead of just the one that matches /*1*/? #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure I understand "just the one that matches but if i understand correctly, the function getPropertySymbolsFromContextualType done the filtering already


In reply to: 87305445 [](ancestors = 87305445)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was wrong -- getPropertySymbolsForContextualType could return undefined if the property it finds has no name, which it might not for some computed properties. (Probably should have a test for this case too)


In reply to: 87303918 [](ancestors = 87303918)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the older version was better. Sorry for the bad suggestion.


In reply to: 88699276 [](ancestors = 88699276,87303918)

@@ -1967,6 +1967,76 @@ namespace ts {
}
}

function isObjectLiteralPropertyDeclaration(node: Node): node is ObjectLiteralElement {
Copy link
Member

@sandersn sandersn Nov 10, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. shouldn't it be ObjectLiteralElementLike? I didn't think JsxAttribute was part of the main ObjectLiteralElement union.
  2. Does this function exist elsewhere? I thought the rest of the compiler might need this too. But maybe it doesn't. #Closed

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. it shouldn't be just objectLiteralElementLike because that only includes "PropertyAssignment | ShorthandPropertyAssignment | MethodDeclaration | AccessorDeclaration | SpreadAssignment;" It should just be ObjectLiteralElement as ObjectlIteralElement is the base interface for ObjectLiteralElementLike and JsxAttributesLike. We want to include Jsx here so that when we goto-definition or find all reference we actually look into contextual type flow from stateless function

In reply to: 87306417 [](ancestors = 87306417)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK, I got the names backward then.


In reply to: 88125144 [](ancestors = 88125144,87306417)

@@ -295,7 +296,7 @@ namespace ts.SignatureHelp {

// findListItemInfo can return undefined if we are not in parent's argument list
// or type argument list. This includes cases where the cursor is:
// - To the right of the closing paren, non-substitution template, or template tail.
// - To the right of the closing parenthesize, non-substitution template, or template tail.
Copy link
Member

@sandersn sandersn Nov 10, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parenthesis #Closed

// This is not guarantee that JSX tag-name is resolved into stateless function component. (that is done in "getSignatureHelpItems")
// i.e
// export function MainButton(props: ButtonProps, context: any): JSX.Element { ... }
// <MainBuntton /*signatureHelp*/
Copy link
Member

@sandersn sandersn Nov 10, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo:MainButton #Closed

if (!signature && candidateSignatures.length) {
// Use the first candidate:
signature = candidateSignatures[0];
}

const useConstructSignatures = callExpression.kind === SyntaxKind.NewExpression || callExpression.expression.kind === SyntaxKind.SuperKeyword;
Copy link
Member

@sandersn sandersn Nov 10, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I liked this form better. You just have to do more formatting now that the predicate is longer. #Closed

*/
function checkJsxAttributesAssignableToTagnameAttributes(openingLikeElement: JsxOpeningLikeElement) {
// The function involves following steps:
// 1. Figure out expected attributes type expected by resolving tag-name of the JSX opening-like element, tagetAttributesType.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo:targetAttributesType

attributesTable = createMap<Symbol>();
if (attributesArray) {
forEach(attributesArray, (attr) => {
if (!filter || (filter && filter(attr))) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

filter [](start = 36, length = 6)

this part of the predicate is implied by the preceding !filter || so you can simplify to !filter || filter(attr)

// i.e <div attr1={10} attr2="string" />
// attr1 and attr2 are treated as JSXAttributes attached in the JsxOpeningLikeElement as "attributes". They resolved to be sourceAttributesType.
const sourceAttributesType = createJsxAttributesTypeFromAttributesProperty(openingLikeElement,
(attribute: Symbol) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(attribute: Symbol) => [](start = 16, length = 22)

can't you just say attribute => ? Or is there some reason to state the type here?

// object literal, lookup the property symbol in the contextual type, and use this for goto-definition.
// For example
// interface Props{
// /first*/prop1: number
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/first*/ [](start = 20, length = 8)

should be /*first*/

const result: Symbol[] = [];
const symbol = contextualType.getProperty(name);
if (symbol) {
result.push(symbol);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why not return [symbol] here? For a union, do you want both the union's synthetic property and all constituent properties? Seems like the union check should come first and it should return result at the end too.

// Foo( { pr/*1*/op1: 10, prop2: true })
const container = getContainingObjectLiteralElement(node);
if (container) {
const contextualType = typeChecker.getContextualType(node.parent.parent as Expression);
Copy link
Member

@sandersn sandersn Nov 18, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

node.parent.parent [](start = 65, length = 18)

why not container.parent here? They are the same thing. Also, contextualType isn't used later -- it's just checked. You could inline this variable, but it's probably better to have propertySymbols return undefined if it doesn't find a contextual type.

return false;
}

function getNameFromObjectLiteralElement(node: ObjectLiteralElement) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getNameFromObjectLiteralElement [](start = 13, length = 31)

I think getTextOfPropertyName from compiler's utilities is exactly the same as this code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, getTextOfPropertyName(node.name) ===getNameFromObjectLiteralElement(node)`


In reply to: 88698797 [](ancestors = 88698797)

// }
// function Foo(arg: Props) {}
// Foo( { pr/*1*/op1: 10, prop2: true })
const container = getContainingObjectLiteralElement(node);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

container [](start = 14, length = 9)

can you name container to something more meaningful like element?

Copy link
Member

@sandersn sandersn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the changes although I still have some questions about gotoDefinition.

@yuit yuit closed this Jan 26, 2017
@yuit yuit deleted the wip-statelessOverload2 branch May 9, 2017 16:04
@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants