Skip to content

Commit 8dadd1b

Browse files
cortinicometa-codesync[bot]
authored andcommitted
Add Swift to yarn format
Summary: Add `yarn format-swift` and `yarn format-check-swift` using Apple swift-format, and compose them into the repository-wide commands. The wrapper selects a repository-provided formatter when available or uses swift-format 6.3 or newer from the Swift toolchain. Missing tools produce environment-specific setup guidance before Swift is skipped. Changelog: [Internal] Differential Revision: D119487615
1 parent b93efc0 commit 8dadd1b

3 files changed

Lines changed: 228 additions & 2 deletions

File tree

.swift-format

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
{
2+
"indentConditionalCompilationBlocks": false,
3+
"indentSwitchCaseLabels": false,
4+
"indentation": {
5+
"spaces": 2
6+
},
7+
"lineBreakAroundMultilineExpressionChainComponents": false,
8+
"lineBreakBeforeControlFlowKeywords": false,
9+
"lineBreakBeforeEachArgument": false,
10+
"lineBreakBeforeEachGenericRequirement": false,
11+
"lineBreakBetweenDeclarationAttributes": false,
12+
"lineLength": 2000,
13+
"maximumBlankLines": 1,
14+
"multiElementCollectionTrailingCommas": true,
15+
"orderedImports": {
16+
"includeConditionalImports": false,
17+
"shouldGroupImports": false
18+
},
19+
"noAssignmentInExpressions": {
20+
"allowedFunctions": [
21+
"XCTAssertNoThrow"
22+
]
23+
},
24+
"prioritizeKeepingFunctionOutputTogether": true,
25+
"reflowMultilineStringLiterals": {
26+
"never": {}
27+
},
28+
"respectsExistingLineBreaks": true,
29+
"rules": {
30+
"AllPublicDeclarationsHaveDocumentation": false,
31+
"AlwaysUseLiteralForEmptyCollectionInit": false,
32+
"AlwaysUseLowerCamelCase": false,
33+
"AmbiguousTrailingClosureOverload": false,
34+
"AvoidRetroactiveConformances": false,
35+
"BeginDocumentationCommentWithOneLineSummary": false,
36+
"DoNotUseSemicolons": true,
37+
"DontRepeatTypeInStaticProperties": false,
38+
"FileScopedDeclarationPrivacy": false,
39+
"FullyIndirectEnum": false,
40+
"GroupNumericLiterals": false,
41+
"IdentifiersMustBeASCII": false,
42+
"NeverForceUnwrap": false,
43+
"NeverUseForceTry": false,
44+
"NeverUseImplicitlyUnwrappedOptionals": false,
45+
"NoAccessLevelOnExtensionDeclaration": false,
46+
"NoAssignmentInExpressions": true,
47+
"NoBlockComments": false,
48+
"NoCasesWithOnlyFallthrough": false,
49+
"NoEmptyLinesOpeningClosingBraces": false,
50+
"NoEmptyTrailingClosureParentheses": false,
51+
"NoLabelsInCasePatterns": false,
52+
"NoLeadingUnderscores": false,
53+
"NoParensAroundConditions": true,
54+
"NoPlaygroundLiterals": false,
55+
"NoVoidReturnOnFunctionSignature": true,
56+
"OmitExplicitReturns": false,
57+
"OneCasePerLine": false,
58+
"OneVariableDeclarationPerLine": true,
59+
"OnlyOneTrailingClosureArgument": false,
60+
"OrderedImports": true,
61+
"ReplaceForEachWithForLoop": false,
62+
"ReturnVoidInsteadOfEmptyTuple": true,
63+
"TypeNamesShouldBeCapitalized": false,
64+
"UseEarlyExits": false,
65+
"UseExplicitNilCheckInConditions": false,
66+
"UseLetInEveryBoundCaseVariable": false,
67+
"UseShorthandTypeNames": false,
68+
"UseSingleLinePropertyGetter": false,
69+
"UseSynthesizedInitializer": false,
70+
"UseTripleSlashForDocumentationComments": false,
71+
"UseWhereClausesInForLoops": false,
72+
"ValidateDocumentationComments": false
73+
},
74+
"spacesAroundRangeFormationOperators": false,
75+
"spacesBeforeEndOfLineComments": 1,
76+
"tabWidth": 8,
77+
"version": 1
78+
}

package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,18 +14,20 @@
1414
"cxx-api-validate": "python -m scripts.cxx-api.parser --validate",
1515
"flow-check": "flow full-check",
1616
"flow": "flow",
17-
"format-check": "yarn format-check-javascript && yarn format-check-cpp && yarn format-check-kotlin && yarn format-check-java && yarn format-check-python",
17+
"format-check": "yarn format-check-javascript && yarn format-check-cpp && yarn format-check-kotlin && yarn format-check-java && yarn format-check-python && yarn format-check-swift",
1818
"format-check-cpp": "node ./scripts/clang-format.js --check",
1919
"format-check-java": "node ./scripts/format-java.js --check",
2020
"format-check-javascript": "prettier --check \"./**/*.{cjs,cts,flow,js,jsx,md,mjs,mts,ts,tsx,yaml,yml}\"",
2121
"format-check-kotlin": "node ./scripts/format-kotlin.js --check",
2222
"format-check-python": "node ./scripts/format-python.js --check",
23-
"format": "yarn format-javascript && yarn format-cpp && yarn format-kotlin && yarn format-java && yarn format-python",
23+
"format-check-swift": "node ./scripts/format-swift.js --check",
24+
"format": "yarn format-javascript && yarn format-cpp && yarn format-kotlin && yarn format-java && yarn format-python && yarn format-swift",
2425
"format-cpp": "node ./scripts/clang-format.js",
2526
"format-java": "node ./scripts/format-java.js",
2627
"format-javascript": "prettier --write \"./**/*.{cjs,cts,flow,js,jsx,md,mjs,mts,ts,tsx,yaml,yml}\"",
2728
"format-kotlin": "node ./scripts/format-kotlin.js",
2829
"format-python": "node ./scripts/format-python.js",
30+
"format-swift": "node ./scripts/format-swift.js",
2931
"featureflags": "yarn --cwd packages/react-native featureflags",
3032
"js-api-diff": "node ./scripts/js-api/diff-api-snapshot",
3133
"lint-markdown": "markdownlint-cli2 2>&1",

scripts/format-swift.js

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @noflow
8+
* @format
9+
*/
10+
11+
'use strict';
12+
13+
const {
14+
IS_META_CHECKOUT,
15+
commandVersion,
16+
findMetaTool,
17+
} = require('./format-utils');
18+
const {spawnSync} = require('node:child_process');
19+
const fs = require('node:fs');
20+
const path = require('node:path');
21+
const {globSync} = require('tinyglobby');
22+
23+
const REPO_ROOT = path.resolve(__dirname, '..');
24+
const CONFIG = path.join(REPO_ROOT, '.swift-format');
25+
const GENERATED_MARKER = Buffer.from('@' + 'generated');
26+
const MINIMUM_SWIFT_FORMAT_MAJOR = 6;
27+
const MINIMUM_SWIFT_FORMAT_MINOR = 3;
28+
const MAX_FILES_PER_PROCESS = 100;
29+
const MAX_HEADER_BYTES = 4096;
30+
const IGNORE = ['**/Pods/**', '**/build/**', '**/node_modules/**'];
31+
32+
function isGenerated(file) {
33+
let fd;
34+
try {
35+
fd = fs.openSync(path.resolve(REPO_ROOT, file), 'r');
36+
const header = Buffer.alloc(MAX_HEADER_BYTES);
37+
const bytesRead = fs.readSync(fd, header, 0, header.length, 0);
38+
return header.subarray(0, bytesRead).includes(GENERATED_MARKER);
39+
} catch (error) {
40+
const message = error instanceof Error ? error.message : String(error);
41+
throw new Error(`Unable to inspect ${file}: ${message}`, {cause: error});
42+
} finally {
43+
if (fd != null) {
44+
fs.closeSync(fd);
45+
}
46+
}
47+
}
48+
49+
function parseSwiftFormatVersion(output) {
50+
const version =
51+
/swift-format(?: version)?[:\s]+(\d+)\.(\d+)/i.exec(output) ??
52+
/Swift version\s+(\d+)\.(\d+)/i.exec(output) ??
53+
/^\s*(\d+)\.(\d+)/.exec(output);
54+
if (version == null) {
55+
return null;
56+
}
57+
const reportedMajor = Number(version[1]);
58+
return reportedMajor >= 100
59+
? [Math.floor(reportedMajor / 100), reportedMajor % 100]
60+
: [reportedMajor, Number(version[2])];
61+
}
62+
63+
function findSwiftFormat() {
64+
const candidates = [];
65+
if (process.env.SWIFT_FORMAT != null && process.env.SWIFT_FORMAT !== '') {
66+
candidates.push([process.env.SWIFT_FORMAT, []]);
67+
} else {
68+
const metaSwiftFormat = findMetaTool(
69+
'tools',
70+
'lint',
71+
'swift-format',
72+
'swift-format',
73+
);
74+
if (metaSwiftFormat != null) {
75+
candidates.push([
76+
metaSwiftFormat.command,
77+
metaSwiftFormat.prefixArguments,
78+
]);
79+
}
80+
candidates.push(['swift-format', []], ['swift', ['format']]);
81+
}
82+
for (const [command, prefixArguments] of candidates) {
83+
const result = commandVersion(command, prefixArguments);
84+
const version = parseSwiftFormatVersion(result.output);
85+
if (
86+
result.status === 0 &&
87+
version != null &&
88+
(version[0] > MINIMUM_SWIFT_FORMAT_MAJOR ||
89+
(version[0] === MINIMUM_SWIFT_FORMAT_MAJOR &&
90+
version[1] >= MINIMUM_SWIFT_FORMAT_MINOR))
91+
) {
92+
return {command, prefixArguments};
93+
}
94+
}
95+
const instructions = IS_META_CHECKOUT
96+
? 'Meta: unset SWIFT_FORMAT and run `tools/lint/swift-format/swift-format --version` from the fbsource root. If that fails, repair your Meta DotSlash setup.'
97+
: 'Please install Swift 6.3 or newer and make sure `swift-format` or `swift` is in your PATH, or set SWIFT_FORMAT=/path/to/swift-format.';
98+
console.warn(
99+
'warning: Skipping Swift formatting because swift-format 6.3 or newer was not found.\n' +
100+
instructions,
101+
);
102+
return null;
103+
}
104+
105+
function main() {
106+
const check = process.argv[2] === '--check';
107+
const swiftFormat = findSwiftFormat();
108+
if (swiftFormat == null) {
109+
return;
110+
}
111+
const files = globSync('**/*.swift', {cwd: REPO_ROOT, ignore: IGNORE}).filter(
112+
file => !isGenerated(file),
113+
);
114+
115+
let exitStatus = 0;
116+
for (let i = 0; i < files.length; i += MAX_FILES_PER_PROCESS) {
117+
const result = spawnSync(
118+
swiftFormat.command,
119+
[
120+
...swiftFormat.prefixArguments,
121+
check ? 'lint' : 'format',
122+
'--configuration',
123+
CONFIG,
124+
...(check ? ['--strict'] : ['--in-place']),
125+
...files.slice(i, i + MAX_FILES_PER_PROCESS),
126+
],
127+
{
128+
cwd: REPO_ROOT,
129+
env: {...process.env, PWD: REPO_ROOT},
130+
stdio: 'inherit',
131+
},
132+
);
133+
if (result.error != null) {
134+
throw result.error;
135+
}
136+
if (result.signal != null) {
137+
throw new Error(`swift-format was terminated by ${result.signal}`);
138+
}
139+
if (result.status !== 0) {
140+
exitStatus = result.status ?? 1;
141+
}
142+
}
143+
process.exitCode = exitStatus;
144+
}
145+
146+
main();

0 commit comments

Comments
 (0)