UNPKG

4.21 kBJavaScriptView Raw
1'use strict';
2
3// Per https://github.com/es-shims/es-shim-api :
4// In every way possible, the package must attempt to make itself
5// robust against the environment being modified after it is required.
6const { prototype } = Function;
7const { toString } = prototype;
8const { apply } = Reflect;
9const { defineProperty, freeze, hasOwnProperty } = Object;
10
11const tokenize = require('./lib/quick-and-dirty-lexer.js');
12
13const memoTable = new WeakMap();
14
15// eslint-disable-next-line complexity
16function arityOf(fun) {
17 if (memoTable.has(fun)) {
18 return memoTable.get(fun);
19 }
20 const str = apply(toString, fun, []);
21
22 // Between the parentheses around the formals.
23 let inFormalParameterList = false;
24 // Count of open brackets ('[', '(', '{') that have been seen without a corresponding
25 // close bracket.
26 let bracketDepth = 0;
27 // Number of formal parameters seen so far. This includes any rest param.
28 let declaredParameterCount = 0;
29 // True if the formal parameter list has a ... pattern.
30 let hasRestParam = false;
31
32 // Action to apply to the next token processed. This allows equiv of LA(1)
33 // without implementing pushback.
34 let onNext = null; // Special action for next token.
35
36 tokenLoop:
37 for (const tok of tokenize(str)) {
38 if (onNext) {
39 const onTok = onNext;
40 onNext = null;
41 onTok(tok);
42 }
43 switch (tok) {
44 case '(':
45 if (!bracketDepth) {
46 inFormalParameterList = true;
47 onNext = function countFirstParam(next) { // eslint-disable-line
48 if (next !== ')') {
49 declaredParameterCount = 1;
50 }
51 };
52 }
53 // fallthrough
54 case '{':
55 case '[':
56 // This works even if str has a dynamic name like
57 // [nameExpression](formal,parameter,list) {body}
58 ++bracketDepth;
59 break;
60 case ',':
61 if (inFormalParameterList && bracketDepth === 1) {
62 ++declaredParameterCount;
63 }
64 break;
65 case '...':
66 if (inFormalParameterList && bracketDepth === 1) {
67 hasRestParam = true;
68 }
69 break;
70 case ')':
71 if (inFormalParameterList && bracketDepth === 1) {
72 break tokenLoop;
73 }
74 // fallthrough
75 case '}': case ']':
76 if (!bracketDepth) {
77 throw new Error(str);
78 }
79 --bracketDepth;
80 break;
81 default:
82 break;
83 }
84 }
85 const record = freeze({
86 __proto__: null,
87 max: declaredParameterCount,
88 usesRest: hasRestParam,
89 });
90 memoTable.set(fun, record);
91 return record;
92}
93
94module.exports = arityOf;
95
96// Per https://github.com/es-shims/es-shim-api :
97
98// require('foo').implementation or require('foo/implementation') is a
99// spec-compliant JS function, that will depend on a receiver
100// (a “this” value) as the spec requires.
101arityOf.implementation = arityOf;
102
103// require('foo').getPolyfill or require('foo/polyfill') is a function
104// that when invoked, will return the most compliant and performant
105// function that it can - if a native version is available, and does
106// not violate the spec, then the native function will be returned -
107// otherwise, either the implementation, or a custom, wrapped version
108// of the native function, will be returned. This is also the result
109// that will be used as the default export.
110arityOf.getPolyfill = function getPolyfill() {
111 // TODO: look for native
112 return arityOf;
113};
114
115// require('foo').shim or require('foo/shim') is a function that when
116// invoked, will call getPolyfill, and if the polyfill doesn’t match
117// the built-in value, will install it into the global environment.
118//
119// The only place the package may modify the environment is within its
120// shim method.
121arityOf.shim = function shim() {
122 if (!hasOwnProperty(prototype, 'maxArity')) {
123 defineProperty(
124 prototype,
125 'maxArity',
126 {
127 configurable: true,
128 get() {
129 return arityOf(this).max;
130 },
131 });
132 }
133 if (!hasOwnProperty(prototype, 'usesRest')) {
134 defineProperty(
135 prototype,
136 'usesRest',
137 {
138 configurable: true,
139 get() {
140 return arityOf(this).usesRest;
141 },
142 });
143 }
144};