Skip to content

Commit 533d172

Browse files
authored
opaque types (#79)
1 parent 47a2b1d commit 533d172

File tree

11 files changed

+248
-42
lines changed

11 files changed

+248
-42
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"releases": [
3+
{ "name": "extract-react-types", "type": "minor" },
4+
{ "name": "kind2string", "type": "minor" }
5+
],
6+
"dependents": [
7+
{
8+
"name": "babel-plugin-extract-react-types",
9+
"type": "patch",
10+
"dependencies": ["extract-react-types"]
11+
},
12+
{
13+
"name": "extract-react-types-loader",
14+
"type": "patch",
15+
"dependencies": ["extract-react-types"]
16+
},
17+
{
18+
"name": "pretty-proptypes",
19+
"type": "patch",
20+
"dependencies": ["extract-react-types", "kind2string"]
21+
}
22+
]
23+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Add support for opaque types

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
"test": "jest",
77
"changeset": "changeset",
88
"bump": "changeset bump",
9-
"release": "changeset release --public",
9+
"release": "yarn build && changeset release --public",
1010
"postinstall": "preconstruct dev",
1111
"dev:pretty-proptypes": "bolt w pretty-proptypes run dev",
1212
"lint": "yarn eslint \"./**/*.js\"",
@@ -63,6 +63,7 @@
6363
"eslint-plugin-prettier": "^3.0.1",
6464
"eslint-plugin-react": "^7.12.4",
6565
"jest-in-case": "^1.0.2",
66+
"preconstruct": "^0.0.81",
6667
"react-markings": "^1.2.0",
6768
"resolve": "^1.10.1",
6869
"strip-indent": "^2.0.0"
@@ -89,7 +90,6 @@
8990
"jest": "^24.7.1",
9091
"jest-in-case": "^1.0.2",
9192
"jsdom": "^11.7.0",
92-
"preconstruct": "^0.0.64",
9393
"prettier": "^1.13.7",
9494
"react": "^16.3.1",
9595
"react-addons-test-utils": "^15.6.2",

packages/extract-react-types/__snapshots__/test.js.snap

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -368,6 +368,41 @@ Object {
368368
}
369369
`;
370370

371+
exports[`OpaqueType test 1`] = `
372+
Object {
373+
"component": Object {
374+
"kind": "generic",
375+
"name": Object {
376+
"kind": "id",
377+
"name": "SomeComponent",
378+
"type": null,
379+
},
380+
"value": Object {
381+
"kind": "object",
382+
"members": Array [
383+
Object {
384+
"key": Object {
385+
"kind": "id",
386+
"name": "ok",
387+
},
388+
"kind": "property",
389+
"optional": true,
390+
"value": Object {
391+
"kind": "generic",
392+
"value": Object {
393+
"kind": "id",
394+
"name": "SomethingId",
395+
},
396+
},
397+
},
398+
],
399+
"referenceIdName": "Props",
400+
},
401+
},
402+
"kind": "program",
403+
}
404+
`;
405+
371406
exports[`Should handle ArrayTypeAnnotations 1`] = `
372407
Object {
373408
"component": Object {

packages/extract-react-types/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,4 @@
4343
"jest": "^24.7.1",
4444
"prettier": "^1.13.7"
4545
}
46-
}
46+
}

packages/extract-react-types/src/index.js

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,34 @@ converters.NewExpression = (path, context): K.New => {
395395
};
396396
};
397397

398+
converters.InterfaceDeclaration = (path, context): K.InterfaceDeclaration => {
399+
return { kind: 'interfaceDeclaration', id: convert(path.get('id'), context) };
400+
};
401+
402+
converters.OpaqueType = (path, context): K.OpaqueType => {
403+
// OpaqueTypes have several optional nodes that exist as a null when not present
404+
// We need to convert these when they exist, and ignore them when they don't;
405+
let supertypePath = path.get('supertype');
406+
let impltypePath = path.get('impltype');
407+
let typeParametersPath = path.get('typeParameters');
408+
409+
// TODO we are having a fight at the moment with id returning a binding, not a node,
410+
// and don't have time to solve this properly - I am pathing it to being working-ish
411+
// here, and will come back to this later. If you find this comment still here and
412+
// want to fix this problem, I encourage you to do is.
413+
414+
let supertype;
415+
let impltype;
416+
let typeParameters;
417+
let id = convert(path.get('id'), context);
418+
419+
if (supertypePath.node) supertype = convert(supertypePath, context);
420+
if (impltypePath.node) impltype = convert(impltypePath, context);
421+
if (typeParametersPath.node) typeParameters = convert(typeParametersPath, context);
422+
423+
return { kind: 'opaqueType', id, supertype, impltype, typeParameters };
424+
};
425+
398426
converters.TypeofTypeAnnotation = (path, context): K.Typeof => {
399427
let type = convert(path.get('argument'), { ...context, mode: 'value' });
400428
let ungeneric = resolveFromGeneric(type);
@@ -742,7 +770,6 @@ converters.Identifier = (path, context): K.Id => {
742770
} else if (context.mode === 'type') {
743771
if (kind === 'reference') {
744772
let bindingPath;
745-
746773
if (isFlowIdentifier(path)) {
747774
let flowBinding = getTypeBinding(path, name);
748775
if (!flowBinding) throw new Error();
@@ -771,15 +798,21 @@ converters.Identifier = (path, context): K.Id => {
771798
}
772799

773800
if (bindingPath) {
774-
if (bindingPath.kind === 'module') {
775-
bindingPath = bindingPath.path;
776-
}
801+
if (name === 'SomethingId')
802+
if (bindingPath.kind === 'module') {
803+
bindingPath = bindingPath.path;
804+
}
777805

778806
// If path is a descendant of bindingPath and share the same name, this is a recursive type.
779807
if (path.isDescendant(bindingPath) && bindingPath.get('id').node.name === name) {
780808
return { kind: 'id', name };
781809
}
782810

811+
// This is a hack that stops horrible regression errors and problems
812+
if (bindingPath.kind === 'unknown') {
813+
return { kind: 'id', name };
814+
}
815+
783816
if (bindingPath.kind !== 'module') {
784817
const convertedValue = convert(bindingPath, context);
785818
return {
@@ -1435,9 +1468,31 @@ function attachComments(source, dest) {
14351468
attachCommentProperty(source, dest, 'innerComments');
14361469
}
14371470

1471+
// This function is from mdn:
1472+
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Errors/Cyclic_object_value#Examples
1473+
const getCircularReplacer = () => {
1474+
const seen = new WeakSet();
1475+
return (key, value) => {
1476+
if (typeof value === 'object' && value !== null) {
1477+
if (seen.has(value)) {
1478+
return;
1479+
}
1480+
seen.add(value);
1481+
}
1482+
return value;
1483+
};
1484+
};
1485+
14381486
function convert(path, context) {
1439-
if (typeof path.get !== 'function')
1440-
throw new Error(`Did not pass a NodePath to convert() ${JSON.stringify(path)}`);
1487+
if (typeof path.get !== 'function') {
1488+
// We were getting incredible unhelpful errors here at times, so we have a circular replacement
1489+
// throw path.identifier;
1490+
let stringedPath = JSON.stringify(path, getCircularReplacer(), 2);
1491+
throw new Error(`Did not pass a NodePath to convert() ${stringedPath}`);
1492+
}
1493+
1494+
// console.log(path.node);
1495+
14411496
let converter = converters[path.type];
14421497
if (!converter) throw new Error(`Missing converter for: ${path.type}`);
14431498
let result = converter(path, context);

packages/extract-react-types/src/kinds.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,16 @@ export type Import = {
155155
external?: boolean
156156
};
157157

158+
export type OpaqueType = {
159+
kind: 'opaqueType',
160+
id: AnyTypeKind,
161+
supertype?: AnyTypeKind,
162+
impltype?: AnyTypeKind,
163+
typeParameters?: AnyTypeKind
164+
};
165+
166+
export type InterfaceDeclaration = { kind: 'interfaceDeclaration', id: AnyTypeKind };
167+
158168
export type Program = { kind: 'program', component?: Property };
159169

160170
export type ExportSpecifier = { kind: 'exportSpecifier', local: Id, exported: Id };

packages/extract-react-types/test.js

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1558,6 +1558,23 @@ const TESTS = [
15581558
15591559
`
15601560
},
1561+
{
1562+
name: 'OpaqueType test',
1563+
typeSystem: 'flow',
1564+
code: `
1565+
opaque type SomethingId = string;
1566+
1567+
type Props = {
1568+
ok?: SomethingId
1569+
}
1570+
1571+
const SomeComponent = function(props: Props) {
1572+
1573+
}
1574+
1575+
export default SomeComponent
1576+
`
1577+
},
15611578
{
15621579
name: 'flow forwardRef default export',
15631580
typeSystem: 'flow',
@@ -1619,7 +1636,7 @@ const TESTS = [
16191636
class Component extends React.Component<Foo<string, {}>> {
16201637
}
16211638
`
1622-
},
1639+
}
16231640
];
16241641

16251642
cases(

packages/kind2string/src/index.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@ const converters = {
7878
id: (type: K.Id, mode: string): string => {
7979
return type.name;
8080
},
81+
// TODO - this is not right and needs to be improved
82+
opaqueType: (type: K.OpaqueType, mode: string): string => {
83+
return convert(type.id);
84+
},
85+
interfaceDeclaration: (type: K.InterfaceDeclaration) => {
86+
return convert(type.id);
87+
},
8188
typeCastExpression: (type: K.TypeCastExpression, mode: string): string => {
8289
return convert(type.expression);
8390
},

packages/pretty-proptypes/package.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,8 @@
3939
"react": "^16.3.1",
4040
"react-addons-test-utils": "^15.6.2",
4141
"react-dom": "^16.3.1"
42+
},
43+
"peerDependencies": {
44+
"react": ">=16"
4245
}
43-
}
46+
}

0 commit comments

Comments
 (0)